Compare commits
16 Commits
00be10f134
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 51b174d330 | |||
| 58361589d8 | |||
| 0681b8bbe4 | |||
| 3cb49fc4f8 | |||
| db714471b8 | |||
| 8c204aad77 | |||
| a6040e7d68 | |||
| 60d2af0a27 | |||
| ac0c464910 | |||
| c9ece65182 | |||
| c519f90149 | |||
| d6a1565503 | |||
| ab3b6bfc9a | |||
| c1a0822227 | |||
| 22bc6a7fef | |||
| fe03bee588 |
+26
-13
@@ -2,9 +2,9 @@
|
||||
|
||||
## 1. 文档范围
|
||||
|
||||
本文档定义 `TCP2UART` 项目的最终 AT 外部协议。
|
||||
本文档定义 `TCP2UART` 项目的 AT 外部协议。
|
||||
|
||||
本文档只描述最终协议模型,不保留任何历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。
|
||||
本文档只描述当前代码实现支持的外部命令,不保留历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。项目结构、任务模型和数据流请阅读 `README.md` 与 `项目代码阅读指南.md`。
|
||||
|
||||
适用对象:
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
- 配置口:`USART1`
|
||||
- 数据口:`USART2`、`USART3`
|
||||
|
||||
### 2.1 固件版本线
|
||||
|
||||
- FreeRTOS + lwIP 版本线从 `V2.0.0` 开始。
|
||||
- 裸机版本线从 `V1.0.0` 开始。
|
||||
- 当前 FreeRTOS 固件基线 release:`TCP2UART RTOS V2.0.0`。
|
||||
- 固件下载:`https://git.furtherverse.com/gaoro-xiao/TCP2UART/releases/tag/V2.0.0`
|
||||
|
||||
职责划分:
|
||||
|
||||
- `USART1`:AT 配置口
|
||||
@@ -49,12 +56,12 @@ SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
||||
|
||||
字段定义:
|
||||
|
||||
- `SYNC`:帧起始标记,建议固定为 `0x7E`
|
||||
- `SYNC`:帧起始标记,固定为 `0x7E`
|
||||
- `LEN_H / LEN_L`:`PAYLOAD` 长度,高字节在前
|
||||
- `SRCID`:单字节源端点 ID
|
||||
- `DSTMASK`:单字节目标端点位图
|
||||
- `PAYLOAD`:负载数据
|
||||
- `TAIL`:帧结束标记,建议固定为 `0x7F`
|
||||
- `TAIL`:帧结束标记,固定为 `0x7F`
|
||||
|
||||
规则:
|
||||
|
||||
@@ -92,7 +99,7 @@ SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
||||
```text
|
||||
AT\r\n
|
||||
AT+MUX?\r\n
|
||||
AT+NET=192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
|
||||
AT+NET=192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00\r\n
|
||||
```
|
||||
|
||||
### 6.2 持久化规则
|
||||
@@ -121,9 +128,11 @@ MUX = 0
|
||||
### 7.2 NET 默认值
|
||||
|
||||
```text
|
||||
NET = 192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01
|
||||
NET = 192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00
|
||||
```
|
||||
|
||||
默认 MAC 为全 0,表示 Flash 中不固化板卡 MAC;运行时使用 `CH390D` 内部 MAC。`AT+?` 与 `AT+NET?` 回显的是当前生效 MAC。
|
||||
|
||||
### 7.3 LINK 默认值
|
||||
|
||||
```text
|
||||
@@ -171,7 +180,7 @@ AT+QUERY\r\n
|
||||
推荐返回格式:
|
||||
|
||||
```text
|
||||
+NET:IP=192.168.1.100,MASK=255.255.255.0,GW=192.168.1.1,MAC=02:00:00:00:00:01
|
||||
+NET:IP=192.168.31.100,MASK=255.255.255.0,GW=192.168.31.1,MAC=<当前生效MAC>
|
||||
+LINK:S1,EN=1,LPORT=8080,RIP=0.0.0.0,RPORT=0,UART=U0
|
||||
+LINK:S2,EN=0,LPORT=8081,RIP=0.0.0.0,RPORT=0,UART=U1
|
||||
+LINK:C1,EN=1,LPORT=9001,RIP=192.168.1.200,RPORT=9000,UART=U1
|
||||
@@ -213,7 +222,7 @@ OK
|
||||
#### 设置 NET
|
||||
|
||||
```text
|
||||
AT+NET=192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
|
||||
AT+NET=192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00\r\n
|
||||
```
|
||||
|
||||
字段顺序:
|
||||
@@ -231,13 +240,13 @@ AT+NET?\r\n
|
||||
返回示例:
|
||||
|
||||
```text
|
||||
+NET:IP=192.168.1.100,MASK=255.255.255.0,GW=192.168.1.1,MAC=02:00:00:00:00:01
|
||||
+NET:IP=192.168.31.100,MASK=255.255.255.0,GW=192.168.31.1,MAC=<当前生效MAC>
|
||||
OK
|
||||
```
|
||||
|
||||
**MAC 设置说明:**
|
||||
|
||||
当MAC设置为全0时,固件将使用硬件MAC地址,此时通过AT+?查询到的MAC地址即为当前生效的硬件MAC地址。
|
||||
当 MAC 设置为全 0 时,固件将使用 `CH390D` 内部 MAC 地址。此时 Flash 内仍保存全 0,不会把内部 MAC 写回 Flash;`AT+?` 和 `AT+NET?` 查询到的 MAC 地址为当前运行时生效的硬件 MAC 地址。
|
||||
|
||||
### 8.5 LINK 类命令
|
||||
|
||||
@@ -268,6 +277,9 @@ ROLE,EN,LPORT,RIP,RPORT,UART
|
||||
- `Server` 与 `Client` 共用同一条 `LINK` 记录模型
|
||||
- `Server` 中 `RIP/RPORT` 可作为允许接入的对端约束或预设对端信息
|
||||
- `Client` 中 `RIP/RPORT` 表示远端目标地址与端口
|
||||
- `Client` 侧当前保留固定 `LPORT` 语义,用于满足部分上位机或现场网络策略对固定源端口的依赖
|
||||
- 为避免固定 `LPORT` 下频繁重连被 lwIP `TIME_WAIT` 长时间占用阻塞,当前固件对 `Client` 主动断开后的释放路径采用 abortive close(RST)而非优雅 `FIN/ACK` 关闭
|
||||
- 因此 `Client` 重连场景下,对端可能观察到 `RST` 或“连接被重置”,这属于当前产品约束下的有意设计取舍,不影响 `AT+LINK` 对 `LPORT` 的配置语义
|
||||
|
||||
#### 查询单条 LINK
|
||||
|
||||
@@ -354,7 +366,7 @@ OK: Defaults restored
|
||||
## 11. 推荐配置流程
|
||||
|
||||
```text
|
||||
AT+NET=192.168.1.123,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
|
||||
AT+NET=192.168.31.123,255.255.255.0,192.168.31.1,00:00:00:00:00:00\r\n
|
||||
AT+LINK=S1,1,10001,0.0.0.0,0,U1\r\n
|
||||
AT+LINK=S2,1,10003,0.0.0.0,0,U1\r\n
|
||||
AT+LINK=C1,1,20001,192.168.1.201,10002,U0\r\n
|
||||
@@ -382,9 +394,10 @@ AT+RESET\r\n
|
||||
1. `AT+SAVE\r\n`
|
||||
2. `AT+RESET\r\n`
|
||||
|
||||
## 13. 相关文件
|
||||
## 13. 相关文档与文件
|
||||
|
||||
- 项目入口:`README.md`
|
||||
- 代码结构和数据流:`项目代码阅读指南.md`
|
||||
- AT 命令实现:`App/config.c`
|
||||
- 配置结构与默认值:`App/config.h`
|
||||
- FreeRTOS 任务定义:`Core/Src/freertos.c`
|
||||
- 调试指导:`工程调试指南.md`
|
||||
|
||||
@@ -28,6 +28,14 @@ extern volatile int32_t g_netif_set_up_err;
|
||||
extern volatile int32_t g_netif_init_ok;
|
||||
|
||||
void app_start_network_tasks(void);
|
||||
void app_request_network_task_stop(void);
|
||||
void app_clear_network_task_stop(void);
|
||||
BaseType_t app_network_task_stop_requested(void);
|
||||
BaseType_t app_network_tasks_are_stopped(void);
|
||||
void app_on_network_task_exit(TaskHandle_t task_handle);
|
||||
void app_request_network_restart(void);
|
||||
void app_clear_network_restart_request(void);
|
||||
BaseType_t app_network_restart_requested(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+121
-11
@@ -13,6 +13,7 @@
|
||||
#include "route_msg.h"
|
||||
#include "app_runtime.h"
|
||||
#include "debug_log.h"
|
||||
#include "ethernetif.h"
|
||||
#include "uart_trans.h"
|
||||
|
||||
#define CONFIG_RX_BUFFER_SIZE 160u
|
||||
@@ -22,6 +23,8 @@
|
||||
static device_config_t g_config;
|
||||
static volatile bool g_reset_requested;
|
||||
static volatile bool g_uart1_tx_busy;
|
||||
static volatile uint32_t g_config_rx_route_fail_count;
|
||||
static volatile route_send_result_t g_config_rx_route_fail_reason;
|
||||
static uint8_t g_uart1_rx_buffer[CONFIG_RX_BUFFER_SIZE];
|
||||
static char g_config_cmd_buffer[CONFIG_CMD_MAX_LEN];
|
||||
static char g_config_response_buffer[CONFIG_TX_BUFFER_SIZE];
|
||||
@@ -113,6 +116,19 @@ static int parse_link_uart(const char *value, uint8_t *uart)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_uart_name(const char *value, uint8_t *uart_index)
|
||||
{
|
||||
if (equals_ignore_case(value, "U0") || equals_ignore_case(value, "UART2")) {
|
||||
*uart_index = LINK_UART_U0;
|
||||
return 0;
|
||||
}
|
||||
if (equals_ignore_case(value, "U1") || equals_ignore_case(value, "UART3")) {
|
||||
*uart_index = LINK_UART_U1;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char *link_uart_to_str(uint8_t uart)
|
||||
{
|
||||
return (uart == LINK_UART_U1) ? "U1" : "U0";
|
||||
@@ -134,6 +150,15 @@ static const char *link_index_to_name(uint32_t index)
|
||||
}
|
||||
}
|
||||
|
||||
static void config_get_display_mac(uint8_t *mac)
|
||||
{
|
||||
if (ethernetif_get_effective_mac(mac) != 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(mac, g_config.net.mac, sizeof(g_config.net.mac));
|
||||
}
|
||||
|
||||
static int parse_link_name(const char *value, uint32_t *index)
|
||||
{
|
||||
if (equals_ignore_case(value, "S1")) {
|
||||
@@ -229,13 +254,15 @@ static at_result_t handle_summary_query(char *response, uint16_t max_len)
|
||||
char mask_str[16];
|
||||
char gw_str[16];
|
||||
char mac_str[18];
|
||||
uint8_t display_mac[6];
|
||||
char rip_str[CONFIG_LINK_COUNT][16];
|
||||
uint32_t i;
|
||||
|
||||
config_ip_to_str(g_config.net.ip, ip_str);
|
||||
config_ip_to_str(g_config.net.mask, mask_str);
|
||||
config_ip_to_str(g_config.net.gw, gw_str);
|
||||
config_mac_to_str(g_config.net.mac, mac_str);
|
||||
config_get_display_mac(display_mac);
|
||||
config_mac_to_str(display_mac, mac_str);
|
||||
for (i = 0; i < CONFIG_LINK_COUNT; ++i) {
|
||||
config_ip_to_str(g_config.links[i].remote_ip, rip_str[i]);
|
||||
}
|
||||
@@ -315,6 +342,14 @@ const device_config_t *config_get(void)
|
||||
return &g_config;
|
||||
}
|
||||
|
||||
uint32_t config_get_uart_baudrate(uint8_t uart_index)
|
||||
{
|
||||
if (uart_index >= CONFIG_UART_COUNT) {
|
||||
return DEFAULT_UART_BAUDRATE;
|
||||
}
|
||||
return g_config.uart_baudrate[uart_index];
|
||||
}
|
||||
|
||||
device_config_t *config_get_mutable(void)
|
||||
{
|
||||
return &g_config;
|
||||
@@ -370,6 +405,39 @@ at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_
|
||||
snprintf(response, max_len, "+MUX:%u\r\nOK\r\n", g_config.mux_mode);
|
||||
return AT_OK;
|
||||
}
|
||||
if (equals_ignore_case(p, "BAUD?")) {
|
||||
snprintf(response, max_len,
|
||||
"+BAUD:U0=%lu,U1=%lu\r\nOK\r\n",
|
||||
(unsigned long)g_config.uart_baudrate[0],
|
||||
(unsigned long)g_config.uart_baudrate[1]);
|
||||
return AT_OK;
|
||||
}
|
||||
if (parse_command_with_value(p, "BAUD", &value)) {
|
||||
char value_copy[48];
|
||||
char *cursor;
|
||||
char *token;
|
||||
uint8_t uart_index;
|
||||
uint32_t baudrate;
|
||||
|
||||
strncpy(value_copy, value, sizeof(value_copy) - 1u);
|
||||
value_copy[sizeof(value_copy) - 1u] = '\0';
|
||||
cursor = value_copy;
|
||||
token = config_next_token(&cursor);
|
||||
if (token == NULL || parse_uart_name(token, &uart_index) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid UART\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
|
||||
token = config_next_token(&cursor);
|
||||
if (token == NULL || parse_u32_value(token, 1200u, 921600u, &baudrate) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid baudrate\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
|
||||
g_config.uart_baudrate[uart_index] = baudrate;
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
if (parse_command_with_value(p, "MUX", &value)) {
|
||||
uint32_t mux_value;
|
||||
if (parse_u32_value(value, 0u, 1u, &mux_value) != 0) {
|
||||
@@ -385,11 +453,13 @@ at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_
|
||||
char mask_str[16];
|
||||
char gw_str[16];
|
||||
char mac_str[18];
|
||||
uint8_t display_mac[6];
|
||||
|
||||
config_ip_to_str(g_config.net.ip, ip_str);
|
||||
config_ip_to_str(g_config.net.mask, mask_str);
|
||||
config_ip_to_str(g_config.net.gw, gw_str);
|
||||
config_mac_to_str(g_config.net.mac, mac_str);
|
||||
config_get_display_mac(display_mac);
|
||||
config_mac_to_str(display_mac, mac_str);
|
||||
snprintf(response, max_len, "+NET:IP=%s,MASK=%s,GW=%s,MAC=%s\r\nOK\r\n", ip_str, mask_str, gw_str, mac_str);
|
||||
return AT_OK;
|
||||
}
|
||||
@@ -570,19 +640,24 @@ void config_uart_idle_handler(void)
|
||||
uint16_t len = CONFIG_RX_BUFFER_SIZE - dma_counter;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
HAL_StatusTypeDef hal_status;
|
||||
route_send_result_t route_result;
|
||||
|
||||
if (g_uart1_tx_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 0u && xConfigQueue != NULL) {
|
||||
(void)route_send_from_isr(xConfigQueue,
|
||||
0u,
|
||||
0u,
|
||||
ROUTE_CONN_UART1,
|
||||
g_uart1_rx_buffer,
|
||||
len,
|
||||
&xHigherPriorityTaskWoken);
|
||||
route_result = route_send_from_isr(xConfigQueue,
|
||||
0u,
|
||||
0u,
|
||||
ROUTE_CONN_UART1,
|
||||
g_uart1_rx_buffer,
|
||||
len,
|
||||
&xHigherPriorityTaskWoken);
|
||||
if (route_result != ROUTE_SEND_OK) {
|
||||
g_config_rx_route_fail_reason = route_result;
|
||||
g_config_rx_route_fail_count += 1u;
|
||||
}
|
||||
}
|
||||
|
||||
hal_status = HAL_UART_DMAStop(&huart1);
|
||||
@@ -625,16 +700,47 @@ static void config_respond_to_uart(route_msg_t *msg, const char *response)
|
||||
uart_channel_t channel = (msg->src_id == ENDPOINT_UART3) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
|
||||
uint8_t frame[ROUTE_MSG_MAX_PAYLOAD + 6u];
|
||||
uint16_t frame_len = 0u;
|
||||
uart_trans_send_result_t uart_result;
|
||||
if (uart_mux_encode_frame(msg->src_id, 0u, (const uint8_t *)response, (uint16_t)strlen(response), frame, &frame_len, sizeof(frame))) {
|
||||
(void)uart_trans_send_buffer(channel, frame, frame_len);
|
||||
uart_result = uart_trans_send_buffer(channel, frame, frame_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
debug_log_printf("[CFG] resp-tx-fail ch=%u rc=%s len=%u\r\n",
|
||||
(unsigned int)channel,
|
||||
uart_trans_send_result_to_str(uart_result),
|
||||
(unsigned int)frame_len);
|
||||
}
|
||||
} else {
|
||||
debug_log_printf("[CFG] resp-enc-fail src=0x%02X len=%u\r\n",
|
||||
(unsigned int)msg->src_id,
|
||||
(unsigned int)strlen(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void config_report_route_failures(uint32_t *reported_route_fail_count)
|
||||
{
|
||||
uint32_t fail_count;
|
||||
route_send_result_t fail_reason;
|
||||
|
||||
if (reported_route_fail_count == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
fail_count = g_config_rx_route_fail_count;
|
||||
fail_reason = g_config_rx_route_fail_reason;
|
||||
if (fail_count != *reported_route_fail_count) {
|
||||
*reported_route_fail_count = fail_count;
|
||||
debug_log_printf("[CFG] rx-route-fail rc=%s cnt=%lu\r\n",
|
||||
route_send_result_to_str(fail_reason),
|
||||
(unsigned long)fail_count);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigTask(void *argument)
|
||||
{
|
||||
route_msg_t *msg;
|
||||
at_result_t result;
|
||||
uint32_t reported_route_fail_count = 0u;
|
||||
|
||||
(void)argument;
|
||||
debug_log_write("[CFG] task-entry\r\n");
|
||||
@@ -642,10 +748,14 @@ void ConfigTask(void *argument)
|
||||
debug_log_write("[CFG] task-ready\r\n");
|
||||
|
||||
for (;;) {
|
||||
if (xQueueReceive(xConfigQueue, &msg, portMAX_DELAY) != pdPASS) {
|
||||
config_report_route_failures(&reported_route_fail_count);
|
||||
|
||||
if (xQueueReceive(xConfigQueue, &msg, pdMS_TO_TICKS(50)) != pdPASS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
config_report_route_failures(&reported_route_fail_count);
|
||||
|
||||
if (msg->len >= sizeof(g_config_cmd_buffer)) {
|
||||
msg->len = sizeof(g_config_cmd_buffer) - 1u;
|
||||
}
|
||||
|
||||
+2
-1
@@ -66,7 +66,7 @@ typedef struct {
|
||||
#define DEFAULT_NET_IP {192, 168, 31, 100}
|
||||
#define DEFAULT_NET_MASK {255, 255, 255, 0}
|
||||
#define DEFAULT_NET_GW {192, 168, 31, 1}
|
||||
#define DEFAULT_NET_MAC {0x02, 0x00, 0x00, 0x00, 0x00, 0x01}
|
||||
#define DEFAULT_NET_MAC {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
#define DEFAULT_UART_BAUDRATE 115200u
|
||||
|
||||
#define DIAG_CH390_RAW_POLL 0
|
||||
@@ -86,6 +86,7 @@ int config_save(void);
|
||||
void config_set_defaults(void);
|
||||
const device_config_t *config_get(void);
|
||||
device_config_t *config_get_mutable(void);
|
||||
uint32_t config_get_uart_baudrate(uint8_t uart_index);
|
||||
at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_len);
|
||||
void ConfigTask(void *argument);
|
||||
void config_uart_idle_handler(void);
|
||||
|
||||
+85
-32
@@ -12,6 +12,22 @@ typedef struct {
|
||||
|
||||
static route_slot_t g_route_slots[ROUTE_MSG_POOL_SIZE];
|
||||
|
||||
const char *route_send_result_to_str(route_send_result_t result)
|
||||
{
|
||||
switch (result) {
|
||||
case ROUTE_SEND_OK:
|
||||
return "ok";
|
||||
case ROUTE_SEND_INVALID_INPUT:
|
||||
return "invalid";
|
||||
case ROUTE_SEND_POOL_EXHAUSTED:
|
||||
return "pool";
|
||||
case ROUTE_SEND_QUEUE_FULL:
|
||||
return "queue";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void route_msg_init(void)
|
||||
{
|
||||
memset(g_route_slots, 0, sizeof(g_route_slots));
|
||||
@@ -108,15 +124,15 @@ void route_msg_free_from_isr(route_msg_t *msg)
|
||||
taskEXIT_CRITICAL_FROM_ISR(saved_interrupt_status);
|
||||
}
|
||||
|
||||
static bool route_prepare(route_msg_t *msg,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len)
|
||||
static route_send_result_t route_prepare(route_msg_t *msg,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len)
|
||||
{
|
||||
if (msg == NULL || data == NULL || len == 0u || len > ROUTE_MSG_MAX_PAYLOAD) {
|
||||
return false;
|
||||
return ROUTE_SEND_INVALID_INPUT;
|
||||
}
|
||||
|
||||
msg->src_id = src_id;
|
||||
@@ -124,51 +140,88 @@ static bool route_prepare(route_msg_t *msg,
|
||||
msg->conn_type = conn_type;
|
||||
msg->len = len;
|
||||
memcpy(msg->data, data, len);
|
||||
return true;
|
||||
return ROUTE_SEND_OK;
|
||||
}
|
||||
|
||||
bool route_send(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
TickType_t wait_ticks)
|
||||
static route_send_result_t route_validate_args(QueueHandle_t queue,
|
||||
const uint8_t *data,
|
||||
uint16_t len)
|
||||
{
|
||||
route_msg_t *msg = route_msg_alloc(wait_ticks);
|
||||
if (queue == NULL || data == NULL || len == 0u || len > ROUTE_MSG_MAX_PAYLOAD) {
|
||||
return ROUTE_SEND_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (!route_prepare(msg, src_id, dst_mask, conn_type, data, len)) {
|
||||
return ROUTE_SEND_OK;
|
||||
}
|
||||
|
||||
route_send_result_t route_send(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
TickType_t wait_ticks)
|
||||
{
|
||||
route_send_result_t result;
|
||||
route_msg_t *msg;
|
||||
|
||||
result = route_validate_args(queue, data, len);
|
||||
if (result != ROUTE_SEND_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
msg = route_msg_alloc(wait_ticks);
|
||||
|
||||
if (msg == NULL) {
|
||||
return ROUTE_SEND_POOL_EXHAUSTED;
|
||||
}
|
||||
|
||||
result = route_prepare(msg, src_id, dst_mask, conn_type, data, len);
|
||||
if (result != ROUTE_SEND_OK) {
|
||||
route_msg_free(msg);
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (xQueueSend(queue, &msg, wait_ticks) != pdPASS) {
|
||||
route_msg_free(msg);
|
||||
return false;
|
||||
return ROUTE_SEND_QUEUE_FULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
return ROUTE_SEND_OK;
|
||||
}
|
||||
|
||||
bool route_send_from_isr(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
BaseType_t *xHigherPriorityTaskWoken)
|
||||
route_send_result_t route_send_from_isr(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
BaseType_t *xHigherPriorityTaskWoken)
|
||||
{
|
||||
route_msg_t *msg = route_msg_alloc_from_isr(xHigherPriorityTaskWoken);
|
||||
route_send_result_t result;
|
||||
route_msg_t *msg;
|
||||
|
||||
if (!route_prepare(msg, src_id, dst_mask, conn_type, data, len)) {
|
||||
result = route_validate_args(queue, data, len);
|
||||
if (result != ROUTE_SEND_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
msg = route_msg_alloc_from_isr(xHigherPriorityTaskWoken);
|
||||
|
||||
if (msg == NULL) {
|
||||
return ROUTE_SEND_POOL_EXHAUSTED;
|
||||
}
|
||||
|
||||
result = route_prepare(msg, src_id, dst_mask, conn_type, data, len);
|
||||
if (result != ROUTE_SEND_OK) {
|
||||
route_msg_free_from_isr(msg);
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (xQueueSendFromISR(queue, &msg, xHigherPriorityTaskWoken) != pdPASS) {
|
||||
route_msg_free_from_isr(msg);
|
||||
return false;
|
||||
return ROUTE_SEND_QUEUE_FULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
return ROUTE_SEND_OK;
|
||||
}
|
||||
|
||||
+22
-14
@@ -29,6 +29,13 @@ typedef enum {
|
||||
ROUTE_CONN_C2
|
||||
} route_conn_type_t;
|
||||
|
||||
typedef enum {
|
||||
ROUTE_SEND_OK = 0,
|
||||
ROUTE_SEND_INVALID_INPUT,
|
||||
ROUTE_SEND_POOL_EXHAUSTED,
|
||||
ROUTE_SEND_QUEUE_FULL
|
||||
} route_send_result_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t src_id;
|
||||
uint8_t dst_mask;
|
||||
@@ -42,20 +49,21 @@ route_msg_t *route_msg_alloc(TickType_t wait_ticks);
|
||||
route_msg_t *route_msg_alloc_from_isr(BaseType_t *xHigherPriorityTaskWoken);
|
||||
void route_msg_free(route_msg_t *msg);
|
||||
void route_msg_free_from_isr(route_msg_t *msg);
|
||||
bool route_send(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
TickType_t wait_ticks);
|
||||
bool route_send_from_isr(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
BaseType_t *xHigherPriorityTaskWoken);
|
||||
const char *route_send_result_to_str(route_send_result_t result);
|
||||
route_send_result_t route_send(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
TickType_t wait_ticks);
|
||||
route_send_result_t route_send_from_isr(QueueHandle_t queue,
|
||||
uint8_t src_id,
|
||||
uint8_t dst_mask,
|
||||
uint8_t conn_type,
|
||||
const uint8_t *data,
|
||||
uint16_t len,
|
||||
BaseType_t *xHigherPriorityTaskWoken);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+67
-2
@@ -16,6 +16,64 @@
|
||||
#include "app_runtime.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
#define CH390_RESTART_HOLD_DOWN_MS 500u
|
||||
#define NETWORK_TASK_DELETE_SETTLE_MS 50u
|
||||
#define CH390_EXPECTED_VENDOR_ID 0x1C00u
|
||||
#define CH390_EXPECTED_PRODUCT_ID 0x9151u
|
||||
|
||||
static void net_poll_wait_for_network_tasks_stop(void)
|
||||
{
|
||||
while (app_network_tasks_are_stopped() == pdFALSE) {
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
}
|
||||
|
||||
static BaseType_t net_poll_restart_network_stack(const device_config_t *cfg)
|
||||
{
|
||||
#if !DIAG_CH390_RAW_POLL
|
||||
ip4_addr_t ipaddr;
|
||||
ip4_addr_t netmask;
|
||||
ip4_addr_t gateway;
|
||||
uint16_t vendor_id;
|
||||
uint16_t product_id;
|
||||
uint8_t revision;
|
||||
|
||||
IP4_ADDR(&ipaddr, cfg->net.ip[0], cfg->net.ip[1], cfg->net.ip[2], cfg->net.ip[3]);
|
||||
IP4_ADDR(&netmask, cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3]);
|
||||
IP4_ADDR(&gateway, cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3]);
|
||||
#endif
|
||||
|
||||
ethernetif_force_link_down();
|
||||
g_netif_ready = pdFALSE;
|
||||
app_request_network_task_stop();
|
||||
net_poll_wait_for_network_tasks_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(NETWORK_TASK_DELETE_SETTLE_MS));
|
||||
vTaskDelay(pdMS_TO_TICKS(CH390_RESTART_HOLD_DOWN_MS));
|
||||
|
||||
#if DIAG_CH390_RAW_POLL
|
||||
ethernetif_diag_ch390_init();
|
||||
#else
|
||||
ethernetif_force_full_recovery(&ipaddr, &netmask, &gateway, cfg->net.mac);
|
||||
vendor_id = ethernetif_ch390_get_vendor_id();
|
||||
product_id = ethernetif_ch390_get_product_id();
|
||||
revision = ethernetif_ch390_get_revision();
|
||||
if ((vendor_id != CH390_EXPECTED_VENDOR_ID) || (product_id != CH390_EXPECTED_PRODUCT_ID)) {
|
||||
debug_log_printf("[NET] restart-recovery id-warn vid=0x%04X pid=0x%04X rev=0x%02X free=%lu min=%lu\r\n",
|
||||
(unsigned int)vendor_id,
|
||||
(unsigned int)product_id,
|
||||
(unsigned int)revision,
|
||||
(unsigned long)xPortGetFreeHeapSize(),
|
||||
(unsigned long)xPortGetMinimumEverFreeHeapSize());
|
||||
}
|
||||
#endif
|
||||
|
||||
app_clear_network_task_stop();
|
||||
g_netif_ready = pdTRUE;
|
||||
app_start_network_tasks();
|
||||
app_clear_network_restart_request();
|
||||
return pdTRUE;
|
||||
}
|
||||
|
||||
void NetPollTask(void *argument)
|
||||
{
|
||||
const device_config_t *cfg;
|
||||
@@ -98,6 +156,11 @@ void NetPollTask(void *argument)
|
||||
debug_log_write("[NET] loop-enter\r\n");
|
||||
loop_logged = pdTRUE;
|
||||
}
|
||||
|
||||
if (app_network_restart_requested() != pdFALSE) {
|
||||
(void)net_poll_restart_network_stack(cfg);
|
||||
}
|
||||
|
||||
(void)xSemaphoreTake(xNetSemaphore, pdMS_TO_TICKS(2));
|
||||
|
||||
#if DIAG_CH390_RAW_POLL
|
||||
@@ -120,8 +183,10 @@ void NetPollTask(void *argument)
|
||||
}
|
||||
}
|
||||
#else
|
||||
ethernetif_poll();
|
||||
ethernetif_check_link();
|
||||
if (g_netif_ready != pdFALSE) {
|
||||
ethernetif_poll();
|
||||
ethernetif_check_link();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
+116
-17
@@ -5,6 +5,8 @@
|
||||
#include "queue.h"
|
||||
#include "lwip/api.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/tcp.h"
|
||||
#include "lwip/tcpip.h"
|
||||
|
||||
#include "app_runtime.h"
|
||||
#include "config.h"
|
||||
@@ -12,7 +14,56 @@
|
||||
#include "ethernetif.h"
|
||||
#include "route_msg.h"
|
||||
|
||||
#define TCP_CLIENT_CONNECT_TIMEOUT_MS 5000
|
||||
#define TCP_CLIENT_CONNECT_TIMEOUT_MS 500
|
||||
#define TCP_CLIENT_RECONNECT_INTERVAL_MS 3000u
|
||||
#define TCP_CLIENT_STOP_POLL_MS 50u
|
||||
|
||||
static BaseType_t tcp_client_stop_requested(void)
|
||||
{
|
||||
return (app_network_task_stop_requested() != pdFALSE) ? pdTRUE : pdFALSE;
|
||||
}
|
||||
|
||||
static BaseType_t tcp_client_delay_with_stop(uint32_t delay_ms)
|
||||
{
|
||||
uint32_t remaining_ms = delay_ms;
|
||||
|
||||
while (remaining_ms > 0u) {
|
||||
uint32_t slice_ms = (remaining_ms > TCP_CLIENT_STOP_POLL_MS) ? TCP_CLIENT_STOP_POLL_MS : remaining_ms;
|
||||
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(slice_ms));
|
||||
remaining_ms -= slice_ms;
|
||||
}
|
||||
|
||||
return (tcp_client_stop_requested() == pdFALSE) ? pdTRUE : pdFALSE;
|
||||
}
|
||||
|
||||
static void tcp_client_abort_and_delete(struct netconn *conn, uint8_t link_index)
|
||||
{
|
||||
struct tcp_pcb *pcb;
|
||||
|
||||
if (conn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pcb = conn->pcb.tcp;
|
||||
if (pcb != NULL) {
|
||||
LOCK_TCPIP_CORE();
|
||||
pcb = conn->pcb.tcp;
|
||||
if (pcb != NULL) {
|
||||
tcp_abort(pcb);
|
||||
conn->pcb.tcp = NULL;
|
||||
conn->state = NETCONN_NONE;
|
||||
debug_log_printf("[CLI] idx=%u abort-close\r\n", (unsigned int)link_index);
|
||||
}
|
||||
UNLOCK_TCPIP_CORE();
|
||||
}
|
||||
|
||||
netconn_delete(conn);
|
||||
}
|
||||
|
||||
static err_t tcp_client_worker(struct netconn *conn, uint8_t link_index)
|
||||
{
|
||||
@@ -22,30 +73,51 @@ static err_t tcp_client_worker(struct netconn *conn, uint8_t link_index)
|
||||
uint8_t src_endpoint = config_link_index_to_endpoint(link_index);
|
||||
err_t err;
|
||||
route_msg_t *tx_msg;
|
||||
route_send_result_t route_result;
|
||||
|
||||
netconn_set_recvtimeout(conn, 10);
|
||||
|
||||
for (;;) {
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
return ERR_CLSD;
|
||||
}
|
||||
|
||||
err = netconn_recv(conn, &buf);
|
||||
if (err == ERR_OK) {
|
||||
do {
|
||||
void *data;
|
||||
uint16_t len;
|
||||
netbuf_data(buf, &data, &len);
|
||||
(void)route_send(xTcpRxQueue,
|
||||
src_endpoint,
|
||||
uart_endpoint,
|
||||
(link_index == CONFIG_LINK_C1) ? ROUTE_CONN_C1 : ROUTE_CONN_C2,
|
||||
(const uint8_t *)data,
|
||||
len,
|
||||
pdMS_TO_TICKS(10));
|
||||
route_result = route_send(xTcpRxQueue,
|
||||
src_endpoint,
|
||||
uart_endpoint,
|
||||
(link_index == CONFIG_LINK_C1) ? ROUTE_CONN_C1 : ROUTE_CONN_C2,
|
||||
(const uint8_t *)data,
|
||||
len,
|
||||
pdMS_TO_TICKS(10));
|
||||
if (route_result != ROUTE_SEND_OK) {
|
||||
debug_log_printf("[CLI] idx=%u rx-route-fail rc=%s len=%u\r\n",
|
||||
(unsigned int)link_index,
|
||||
route_send_result_to_str(route_result),
|
||||
(unsigned int)len);
|
||||
netbuf_delete(buf);
|
||||
return ERR_CLSD;
|
||||
}
|
||||
} while (netbuf_next(buf) >= 0);
|
||||
netbuf_delete(buf);
|
||||
} else if (err != ERR_TIMEOUT) {
|
||||
} else if (err == ERR_TIMEOUT) {
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
return ERR_CLSD;
|
||||
}
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
|
||||
while (xQueueReceive(xLinkTxQueues[link_index], &tx_msg, 0) == pdPASS) {
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
route_msg_free(tx_msg);
|
||||
return ERR_CLSD;
|
||||
}
|
||||
err = netconn_write(conn, tx_msg->data, tx_msg->len, NETCONN_COPY);
|
||||
route_msg_free(tx_msg);
|
||||
if (err != ERR_OK) {
|
||||
@@ -69,28 +141,44 @@ static void tcp_client_task(uint8_t link_index)
|
||||
first_connect_deferred = (link_index == CONFIG_LINK_C1) ? 1u : 0u;
|
||||
|
||||
for (;;) {
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((g_netif_ready == pdFALSE) || (ethernetif_link_is_up() == 0u)) {
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
goto exit_task;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
cfg = config_get();
|
||||
if (cfg->links[link_index].enabled == 0u) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
break;
|
||||
}
|
||||
if (tcp_client_delay_with_stop(500u) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
delay_ms = (cfg->reconnect_interval_ms == 0u) ? 3000u : cfg->reconnect_interval_ms;
|
||||
delay_ms = TCP_CLIENT_RECONNECT_INTERVAL_MS;
|
||||
|
||||
if (first_connect_deferred != 0u) {
|
||||
first_connect_deferred = 0u;
|
||||
debug_log_write("[CLI] C1 first-connect defer\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
conn = netconn_new(NETCONN_TCP);
|
||||
if (conn == NULL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -102,7 +190,9 @@ static void tcp_client_task(uint8_t link_index)
|
||||
(int)err,
|
||||
(unsigned int)cfg->links[link_index].local_port);
|
||||
netconn_delete(conn);
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -140,10 +230,19 @@ static void tcp_client_task(uint8_t link_index)
|
||||
}
|
||||
}
|
||||
|
||||
netconn_close(conn);
|
||||
netconn_delete(conn);
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
tcp_client_abort_and_delete(conn, link_index);
|
||||
if (tcp_client_stop_requested() != pdFALSE) {
|
||||
break;
|
||||
}
|
||||
if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
exit_task:
|
||||
netconn_thread_cleanup();
|
||||
app_on_network_task_exit(xTaskGetCurrentTaskHandle());
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void TcpCliTask_C1(void *argument)
|
||||
|
||||
+86
-14
@@ -11,7 +11,33 @@
|
||||
#include "debug_log.h"
|
||||
#include "route_msg.h"
|
||||
|
||||
static void tcp_server_worker(struct netconn *conn, uint8_t link_index)
|
||||
#define TCP_SERVER_ACCEPT_TIMEOUT_MS 100
|
||||
#define TCP_SERVER_STOP_POLL_MS 50u
|
||||
|
||||
static BaseType_t tcp_server_stop_requested(void)
|
||||
{
|
||||
return (app_network_task_stop_requested() != pdFALSE) ? pdTRUE : pdFALSE;
|
||||
}
|
||||
|
||||
static BaseType_t tcp_server_delay_with_stop(uint32_t delay_ms)
|
||||
{
|
||||
uint32_t remaining_ms = delay_ms;
|
||||
|
||||
while (remaining_ms > 0u) {
|
||||
uint32_t slice_ms = (remaining_ms > TCP_SERVER_STOP_POLL_MS) ? TCP_SERVER_STOP_POLL_MS : remaining_ms;
|
||||
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(slice_ms));
|
||||
remaining_ms -= slice_ms;
|
||||
}
|
||||
|
||||
return (tcp_server_stop_requested() == pdFALSE) ? pdTRUE : pdFALSE;
|
||||
}
|
||||
|
||||
static err_t tcp_server_worker(struct netconn *conn, uint8_t link_index)
|
||||
{
|
||||
struct netbuf *buf;
|
||||
const device_config_t *cfg = config_get();
|
||||
@@ -19,37 +45,60 @@ static void tcp_server_worker(struct netconn *conn, uint8_t link_index)
|
||||
uint8_t src_endpoint = config_link_index_to_endpoint(link_index);
|
||||
err_t err;
|
||||
route_msg_t *tx_msg;
|
||||
route_send_result_t route_result;
|
||||
|
||||
netconn_set_recvtimeout(conn, 10);
|
||||
|
||||
for (;;) {
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
return ERR_CLSD;
|
||||
}
|
||||
|
||||
err = netconn_recv(conn, &buf);
|
||||
if (err == ERR_OK) {
|
||||
do {
|
||||
void *data;
|
||||
uint16_t len;
|
||||
netbuf_data(buf, &data, &len);
|
||||
(void)route_send(xTcpRxQueue,
|
||||
src_endpoint,
|
||||
uart_endpoint,
|
||||
(link_index == CONFIG_LINK_S1) ? ROUTE_CONN_S1 : ROUTE_CONN_S2,
|
||||
(const uint8_t *)data,
|
||||
len,
|
||||
pdMS_TO_TICKS(10));
|
||||
route_result = route_send(xTcpRxQueue,
|
||||
src_endpoint,
|
||||
uart_endpoint,
|
||||
(link_index == CONFIG_LINK_S1) ? ROUTE_CONN_S1 : ROUTE_CONN_S2,
|
||||
(const uint8_t *)data,
|
||||
len,
|
||||
pdMS_TO_TICKS(10));
|
||||
if (route_result != ROUTE_SEND_OK) {
|
||||
debug_log_printf("[SRV] idx=%u rx-route-fail rc=%s len=%u\r\n",
|
||||
(unsigned int)link_index,
|
||||
route_send_result_to_str(route_result),
|
||||
(unsigned int)len);
|
||||
netbuf_delete(buf);
|
||||
return ERR_CLSD;
|
||||
}
|
||||
} while (netbuf_next(buf) >= 0);
|
||||
netbuf_delete(buf);
|
||||
} else if (err != ERR_TIMEOUT) {
|
||||
} else if (err == ERR_TIMEOUT) {
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
return ERR_CLSD;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
while (xQueueReceive(xLinkTxQueues[link_index], &tx_msg, 0) == pdPASS) {
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
route_msg_free(tx_msg);
|
||||
return ERR_CLSD;
|
||||
}
|
||||
err = netconn_write(conn, tx_msg->data, tx_msg->len, NETCONN_COPY);
|
||||
route_msg_free(tx_msg);
|
||||
if (err != ERR_OK) {
|
||||
return;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tcp_server_task(uint8_t link_index)
|
||||
@@ -61,31 +110,49 @@ static void tcp_server_task(uint8_t link_index)
|
||||
netconn_thread_init();
|
||||
|
||||
for (;;) {
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (g_netif_ready == pdFALSE) {
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
goto exit_task;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
cfg = config_get();
|
||||
if (cfg->links[link_index].enabled == 0u) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
if (tcp_server_stop_requested() != pdFALSE) {
|
||||
break;
|
||||
}
|
||||
if (tcp_server_delay_with_stop(500u) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
listener = netconn_new(NETCONN_TCP);
|
||||
if (listener == NULL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
if (tcp_server_delay_with_stop(500u) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
netconn_set_recvtimeout(listener, TCP_SERVER_ACCEPT_TIMEOUT_MS);
|
||||
|
||||
if (netconn_bind(listener, IP_ADDR_ANY, cfg->links[link_index].local_port) != ERR_OK ||
|
||||
netconn_listen(listener) != ERR_OK) {
|
||||
netconn_delete(listener);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
if (tcp_server_delay_with_stop(500u) == pdFALSE) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (cfg->links[link_index].enabled == 0u) {
|
||||
if (tcp_server_stop_requested() != pdFALSE || cfg->links[link_index].enabled == 0u) {
|
||||
break;
|
||||
}
|
||||
if (netconn_accept(listener, &newconn) == ERR_OK) {
|
||||
@@ -98,6 +165,11 @@ static void tcp_server_task(uint8_t link_index)
|
||||
netconn_close(listener);
|
||||
netconn_delete(listener);
|
||||
}
|
||||
|
||||
exit_task:
|
||||
netconn_thread_cleanup();
|
||||
app_on_network_task_exit(xTaskGetCurrentTaskHandle());
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void TcpSrvTask_S1(void *argument)
|
||||
|
||||
+243
-57
@@ -32,10 +32,27 @@ typedef struct {
|
||||
volatile uint16_t tx_tail;
|
||||
volatile uint16_t tx_dma_len;
|
||||
volatile uint8_t tx_busy;
|
||||
volatile uint8_t tx_kick_fail_logged;
|
||||
} uart_channel_ctx_t;
|
||||
|
||||
static uart_channel_ctx_t g_channels[UART_CHANNEL_MAX];
|
||||
|
||||
const char *uart_trans_send_result_to_str(uart_trans_send_result_t result)
|
||||
{
|
||||
switch (result) {
|
||||
case UART_TRANS_SEND_OK:
|
||||
return "ok";
|
||||
case UART_TRANS_SEND_INVALID_INPUT:
|
||||
return "invalid";
|
||||
case UART_TRANS_SEND_RING_FULL:
|
||||
return "full";
|
||||
case UART_TRANS_SEND_KICK_FAILED:
|
||||
return "kick";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t ring_used(uint16_t head, uint16_t tail, uint16_t size)
|
||||
{
|
||||
return (head >= tail) ? (head - tail) : (size - tail + head);
|
||||
@@ -66,20 +83,21 @@ static void process_rx_snapshot(uart_channel_t channel)
|
||||
}
|
||||
}
|
||||
|
||||
static void kick_tx(uart_channel_t channel)
|
||||
static uart_trans_send_result_t kick_tx(uart_channel_t channel)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
uint16_t available;
|
||||
uint16_t chunk;
|
||||
uint16_t tail;
|
||||
uint16_t i;
|
||||
|
||||
if (ctx->tx_busy != 0u) {
|
||||
return;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
available = ring_used(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE);
|
||||
if (available == 0u) {
|
||||
return;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
chunk = available;
|
||||
@@ -87,16 +105,28 @@ static void kick_tx(uart_channel_t channel)
|
||||
chunk = UART_TX_DMA_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
tail = ctx->tx_tail;
|
||||
for (i = 0; i < chunk; ++i) {
|
||||
ctx->tx_dma_buffer[i] = ctx->tx_ring[ctx->tx_tail];
|
||||
ctx->tx_tail = (uint16_t)((ctx->tx_tail + 1u) % UART_TX_RING_BUFFER_SIZE);
|
||||
ctx->tx_dma_buffer[i] = ctx->tx_ring[tail];
|
||||
tail = (uint16_t)((tail + 1u) % UART_TX_RING_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, chunk) != HAL_OK) {
|
||||
ctx->tx_dma_len = 0u;
|
||||
if (ctx->tx_kick_fail_logged == 0u) {
|
||||
debug_log_printf("[UART] kick-fail ch=%u len=%u\r\n",
|
||||
(unsigned int)channel,
|
||||
(unsigned int)chunk);
|
||||
ctx->tx_kick_fail_logged = 1u;
|
||||
}
|
||||
return UART_TRANS_SEND_KICK_FAILED;
|
||||
}
|
||||
|
||||
ctx->tx_tail = tail;
|
||||
ctx->tx_dma_len = chunk;
|
||||
ctx->tx_busy = 1u;
|
||||
if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, chunk) != HAL_OK) {
|
||||
ctx->tx_busy = 0u;
|
||||
}
|
||||
ctx->tx_kick_fail_logged = 0u;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
static uint16_t uart_ring_available(uart_channel_t channel)
|
||||
@@ -143,6 +173,7 @@ static void uart_route_raw_channel(uart_channel_t channel)
|
||||
uint16_t len;
|
||||
uint8_t uart_endpoint = (channel == UART_CHANNEL_U1) ? ENDPOINT_UART3 : ENDPOINT_UART2;
|
||||
uint32_t i;
|
||||
route_send_result_t route_result;
|
||||
|
||||
len = uart_ring_read(channel, buffer, sizeof(buffer));
|
||||
if (len == 0u) {
|
||||
@@ -154,38 +185,143 @@ static void uart_route_raw_channel(uart_channel_t channel)
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)route_send(xLinkTxQueues[i],
|
||||
uart_endpoint,
|
||||
config_link_index_to_endpoint((uint8_t)i),
|
||||
(channel == UART_CHANNEL_U1) ? ROUTE_CONN_UART3 : ROUTE_CONN_UART2,
|
||||
buffer,
|
||||
len,
|
||||
pdMS_TO_TICKS(10));
|
||||
route_result = route_send(xLinkTxQueues[i],
|
||||
uart_endpoint,
|
||||
config_link_index_to_endpoint((uint8_t)i),
|
||||
(channel == UART_CHANNEL_U1) ? ROUTE_CONN_UART3 : ROUTE_CONN_UART2,
|
||||
buffer,
|
||||
len,
|
||||
pdMS_TO_TICKS(10));
|
||||
if (route_result != ROUTE_SEND_OK) {
|
||||
debug_log_printf("[UART] raw-route-fail idx=%u rc=%s len=%u\r\n",
|
||||
(unsigned int)i,
|
||||
route_send_result_to_str(route_result),
|
||||
(unsigned int)len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_send_tcp_msg_to_uarts(route_msg_t *msg)
|
||||
static uart_trans_send_result_t uart_send_tcp_msg_chunk(route_msg_t *msg,
|
||||
uint16_t offset,
|
||||
uint16_t *accepted_len)
|
||||
{
|
||||
uint8_t frame[ROUTE_MSG_MAX_PAYLOAD + 6u];
|
||||
uint16_t frame_len = 0u;
|
||||
uint16_t remaining;
|
||||
uint16_t chunk_len;
|
||||
uint8_t uart_mask;
|
||||
uart_trans_send_result_t uart_result;
|
||||
|
||||
if ((msg->dst_mask & ENDPOINT_UART2) != 0u) {
|
||||
if (config_get()->mux_mode == MUX_MODE_FRAME) {
|
||||
if (uart_mux_encode_frame(msg->src_id, ENDPOINT_UART2, msg->data, msg->len, frame, &frame_len, sizeof(frame))) {
|
||||
(void)uart_trans_send_buffer(UART_CHANNEL_U0, frame, frame_len);
|
||||
}
|
||||
} else {
|
||||
(void)uart_trans_send_buffer(UART_CHANNEL_U0, msg->data, msg->len);
|
||||
}
|
||||
if (accepted_len == NULL || msg == NULL || offset >= msg->len) {
|
||||
return UART_TRANS_SEND_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if ((msg->dst_mask & ENDPOINT_UART3) != 0u) {
|
||||
*accepted_len = 0u;
|
||||
|
||||
uart_mask = (uint8_t)(msg->dst_mask & (ENDPOINT_UART2 | ENDPOINT_UART3));
|
||||
if ((msg->dst_mask != uart_mask) ||
|
||||
(uart_mask != ENDPOINT_UART2 && uart_mask != ENDPOINT_UART3)) {
|
||||
return UART_TRANS_SEND_INVALID_INPUT;
|
||||
}
|
||||
|
||||
remaining = (uint16_t)(msg->len - offset);
|
||||
|
||||
if (uart_mask == ENDPOINT_UART2) {
|
||||
if (config_get()->mux_mode == MUX_MODE_FRAME) {
|
||||
if (uart_mux_encode_frame(msg->src_id, ENDPOINT_UART3, msg->data, msg->len, frame, &frame_len, sizeof(frame))) {
|
||||
(void)uart_trans_send_buffer(UART_CHANNEL_U1, frame, frame_len);
|
||||
chunk_len = remaining;
|
||||
if (chunk_len > (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u - 6u)) {
|
||||
chunk_len = (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u - 6u);
|
||||
}
|
||||
} else {
|
||||
(void)uart_trans_send_buffer(UART_CHANNEL_U1, msg->data, msg->len);
|
||||
if (!uart_mux_encode_frame(msg->src_id, ENDPOINT_UART2, &msg->data[offset], chunk_len, frame, &frame_len, sizeof(frame))) {
|
||||
return UART_TRANS_SEND_INVALID_INPUT;
|
||||
}
|
||||
uart_result = uart_trans_send_buffer(UART_CHANNEL_U0, frame, frame_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
return uart_result;
|
||||
}
|
||||
*accepted_len = chunk_len;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
chunk_len = remaining;
|
||||
if (chunk_len > (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u)) {
|
||||
chunk_len = (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u);
|
||||
}
|
||||
uart_result = uart_trans_send_buffer(UART_CHANNEL_U0, &msg->data[offset], chunk_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
return uart_result;
|
||||
}
|
||||
*accepted_len = chunk_len;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
if (config_get()->mux_mode == MUX_MODE_FRAME) {
|
||||
chunk_len = remaining;
|
||||
if (chunk_len > (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u - 6u)) {
|
||||
chunk_len = (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u - 6u);
|
||||
}
|
||||
if (!uart_mux_encode_frame(msg->src_id, ENDPOINT_UART3, &msg->data[offset], chunk_len, frame, &frame_len, sizeof(frame))) {
|
||||
return UART_TRANS_SEND_INVALID_INPUT;
|
||||
}
|
||||
uart_result = uart_trans_send_buffer(UART_CHANNEL_U1, frame, frame_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
return uart_result;
|
||||
}
|
||||
*accepted_len = chunk_len;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
chunk_len = remaining;
|
||||
if (chunk_len > (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u)) {
|
||||
chunk_len = (uint16_t)(UART_TX_RING_BUFFER_SIZE - 1u);
|
||||
}
|
||||
uart_result = uart_trans_send_buffer(UART_CHANNEL_U1, &msg->data[offset], chunk_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
return uart_result;
|
||||
}
|
||||
*accepted_len = chunk_len;
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
static void uart_try_advance_pending_tcp_msg(route_msg_t **pending_tcp_msg,
|
||||
uint16_t *pending_tcp_offset,
|
||||
uart_trans_send_result_t *pending_tcp_result)
|
||||
{
|
||||
route_msg_t *msg;
|
||||
uart_trans_send_result_t uart_result;
|
||||
uint16_t accepted_len;
|
||||
|
||||
if (pending_tcp_msg == NULL || pending_tcp_offset == NULL || pending_tcp_result == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
msg = *pending_tcp_msg;
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
accepted_len = 0u;
|
||||
uart_result = uart_send_tcp_msg_chunk(msg, *pending_tcp_offset, &accepted_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
if (uart_result != *pending_tcp_result) {
|
||||
debug_log_printf("[UART] tcp-pend src=0x%02X dst=0x%02X off=%u rc=%s\r\n",
|
||||
(unsigned int)msg->src_id,
|
||||
(unsigned int)msg->dst_mask,
|
||||
(unsigned int)(*pending_tcp_offset),
|
||||
uart_trans_send_result_to_str(uart_result));
|
||||
*pending_tcp_result = uart_result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*pending_tcp_offset = (uint16_t)(*pending_tcp_offset + accepted_len);
|
||||
*pending_tcp_result = UART_TRANS_SEND_OK;
|
||||
if (*pending_tcp_offset >= msg->len) {
|
||||
route_msg_free(msg);
|
||||
*pending_tcp_msg = NULL;
|
||||
*pending_tcp_offset = 0u;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,18 +330,26 @@ static void uart_route_mux_frame(uart_channel_t source_channel, const uart_mux_f
|
||||
{
|
||||
const device_config_t *cfg = config_get();
|
||||
uint32_t i;
|
||||
uint8_t endpoint;
|
||||
uint8_t source_conn = (source_channel == UART_CHANNEL_U1) ? ROUTE_CONN_UART3 : ROUTE_CONN_UART2;
|
||||
uint8_t out_frame[ROUTE_MSG_MAX_PAYLOAD + 6u];
|
||||
uint16_t out_frame_len = 0u;
|
||||
route_send_result_t route_result;
|
||||
uart_trans_send_result_t uart_result;
|
||||
|
||||
if (frame->dst_mask == 0u) {
|
||||
(void)route_send(xConfigQueue,
|
||||
frame->src_id,
|
||||
0u,
|
||||
source_conn,
|
||||
frame->payload,
|
||||
frame->payload_len,
|
||||
pdMS_TO_TICKS(10));
|
||||
route_result = route_send(xConfigQueue,
|
||||
frame->src_id,
|
||||
0u,
|
||||
source_conn,
|
||||
frame->payload,
|
||||
frame->payload_len,
|
||||
pdMS_TO_TICKS(10));
|
||||
if (route_result != ROUTE_SEND_OK) {
|
||||
debug_log_printf("[UART] mux-cfg-fail rc=%s len=%u\r\n",
|
||||
route_send_result_to_str(route_result),
|
||||
(unsigned int)frame->payload_len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,21 +357,41 @@ static void uart_route_mux_frame(uart_channel_t source_channel, const uart_mux_f
|
||||
if (cfg->links[i].enabled == 0u) {
|
||||
continue;
|
||||
}
|
||||
uint8_t endpoint = config_link_index_to_endpoint((uint8_t)i);
|
||||
endpoint = config_link_index_to_endpoint((uint8_t)i);
|
||||
if ((frame->dst_mask & endpoint) != 0u) {
|
||||
(void)route_send(xLinkTxQueues[i], frame->src_id, endpoint, source_conn, frame->payload, frame->payload_len, pdMS_TO_TICKS(10));
|
||||
route_result = route_send(xLinkTxQueues[i], frame->src_id, endpoint, source_conn, frame->payload, frame->payload_len, pdMS_TO_TICKS(10));
|
||||
if (route_result != ROUTE_SEND_OK) {
|
||||
debug_log_printf("[UART] mux-route-fail idx=%u rc=%s len=%u\r\n",
|
||||
(unsigned int)i,
|
||||
route_send_result_to_str(route_result),
|
||||
(unsigned int)frame->payload_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((frame->dst_mask & ENDPOINT_UART2) != 0u && source_channel != UART_CHANNEL_U0) {
|
||||
if (uart_mux_encode_frame(frame->src_id, ENDPOINT_UART2, frame->payload, frame->payload_len, out_frame, &out_frame_len, sizeof(out_frame))) {
|
||||
(void)uart_trans_send_buffer(UART_CHANNEL_U0, out_frame, out_frame_len);
|
||||
uart_result = uart_trans_send_buffer(UART_CHANNEL_U0, out_frame, out_frame_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
debug_log_printf("[UART] mux-u0-tx-fail rc=%s len=%u\r\n",
|
||||
uart_trans_send_result_to_str(uart_result),
|
||||
(unsigned int)out_frame_len);
|
||||
}
|
||||
} else {
|
||||
debug_log_printf("[UART] mux-u0-enc-fail len=%u\r\n", (unsigned int)frame->payload_len);
|
||||
}
|
||||
}
|
||||
|
||||
if ((frame->dst_mask & ENDPOINT_UART3) != 0u && source_channel != UART_CHANNEL_U1) {
|
||||
if (uart_mux_encode_frame(frame->src_id, ENDPOINT_UART3, frame->payload, frame->payload_len, out_frame, &out_frame_len, sizeof(out_frame))) {
|
||||
(void)uart_trans_send_buffer(UART_CHANNEL_U1, out_frame, out_frame_len);
|
||||
uart_result = uart_trans_send_buffer(UART_CHANNEL_U1, out_frame, out_frame_len);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
debug_log_printf("[UART] mux-u1-tx-fail rc=%s len=%u\r\n",
|
||||
uart_trans_send_result_to_str(uart_result),
|
||||
(unsigned int)out_frame_len);
|
||||
}
|
||||
} else {
|
||||
debug_log_printf("[UART] mux-u1-enc-fail len=%u\r\n", (unsigned int)frame->payload_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,13 +405,6 @@ int uart_trans_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uart_trans_config(uint8_t uart_index, uint32_t baudrate)
|
||||
{
|
||||
UART_HandleTypeDef *huart = (uart_index == LINK_UART_U1) ? &huart3 : &huart2;
|
||||
huart->Init.BaudRate = baudrate;
|
||||
return (HAL_UART_Init(huart) == HAL_OK) ? 0 : -1;
|
||||
}
|
||||
|
||||
int uart_trans_start_all(void)
|
||||
{
|
||||
uint32_t i;
|
||||
@@ -273,22 +430,44 @@ int uart_trans_start_all(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool uart_trans_send_buffer(uart_channel_t channel, const uint8_t *data, uint16_t len)
|
||||
uart_trans_send_result_t uart_trans_send_buffer(uart_channel_t channel, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
uart_channel_ctx_t *ctx;
|
||||
uart_trans_send_result_t uart_result;
|
||||
uint16_t original_head;
|
||||
uint16_t written = 0u;
|
||||
|
||||
if (data == NULL || len == 0u) {
|
||||
return false;
|
||||
if (channel >= UART_CHANNEL_MAX || data == NULL || len == 0u || len >= UART_TX_RING_BUFFER_SIZE) {
|
||||
return UART_TRANS_SEND_INVALID_INPUT;
|
||||
}
|
||||
|
||||
while (written < len && ring_free(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE) > 0u) {
|
||||
ctx = &g_channels[channel];
|
||||
if (ctx->huart == NULL) {
|
||||
return UART_TRANS_SEND_INVALID_INPUT;
|
||||
}
|
||||
|
||||
taskENTER_CRITICAL();
|
||||
original_head = ctx->tx_head;
|
||||
if (ring_free(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE) < len) {
|
||||
taskEXIT_CRITICAL();
|
||||
return UART_TRANS_SEND_RING_FULL;
|
||||
}
|
||||
|
||||
while (written < len) {
|
||||
ctx->tx_ring[ctx->tx_head] = data[written++];
|
||||
ctx->tx_head = (uint16_t)((ctx->tx_head + 1u) % UART_TX_RING_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
kick_tx(channel);
|
||||
return (written == len);
|
||||
uart_result = kick_tx(channel);
|
||||
if (uart_result != UART_TRANS_SEND_OK) {
|
||||
ctx->tx_head = original_head;
|
||||
taskEXIT_CRITICAL();
|
||||
return uart_result;
|
||||
}
|
||||
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
return UART_TRANS_SEND_OK;
|
||||
}
|
||||
|
||||
void uart_trans_notify_rx_from_isr(uart_channel_t channel, BaseType_t *xHigherPriorityTaskWoken)
|
||||
@@ -412,8 +591,11 @@ void UartRxTask(void *argument)
|
||||
uint32_t notify_value;
|
||||
BaseType_t notified;
|
||||
route_msg_t *msg;
|
||||
route_msg_t *pending_tcp_msg = NULL;
|
||||
uint16_t pending_tcp_offset = 0u;
|
||||
uart_mux_frame_t frame;
|
||||
const device_config_t *cfg;
|
||||
uart_trans_send_result_t pending_tcp_result = UART_TRANS_SEND_OK;
|
||||
|
||||
(void)argument;
|
||||
if (uart_trans_start_all() != 0) {
|
||||
@@ -439,9 +621,13 @@ void UartRxTask(void *argument)
|
||||
g_channels[UART_CHANNEL_U1].tx_busy = 0u;
|
||||
}
|
||||
|
||||
while (xQueueReceive(xTcpRxQueue, &msg, 0) == pdPASS) {
|
||||
uart_send_tcp_msg_to_uarts(msg);
|
||||
route_msg_free(msg);
|
||||
uart_try_advance_pending_tcp_msg(&pending_tcp_msg, &pending_tcp_offset, &pending_tcp_result);
|
||||
|
||||
while (pending_tcp_msg == NULL && xQueueReceive(xTcpRxQueue, &msg, 0) == pdPASS) {
|
||||
pending_tcp_msg = msg;
|
||||
pending_tcp_offset = 0u;
|
||||
pending_tcp_result = UART_TRANS_SEND_OK;
|
||||
uart_try_advance_pending_tcp_msg(&pending_tcp_msg, &pending_tcp_offset, &pending_tcp_result);
|
||||
}
|
||||
|
||||
cfg = config_get();
|
||||
|
||||
+9
-2
@@ -16,6 +16,13 @@ typedef enum {
|
||||
UART_CHANNEL_MAX
|
||||
} uart_channel_t;
|
||||
|
||||
typedef enum {
|
||||
UART_TRANS_SEND_OK = 0,
|
||||
UART_TRANS_SEND_INVALID_INPUT,
|
||||
UART_TRANS_SEND_RING_FULL,
|
||||
UART_TRANS_SEND_KICK_FAILED
|
||||
} uart_trans_send_result_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t src_id;
|
||||
uint8_t dst_mask;
|
||||
@@ -29,9 +36,9 @@ typedef struct {
|
||||
#define UART_TX_RING_BUFFER_SIZE 256u
|
||||
|
||||
int uart_trans_init(void);
|
||||
int uart_trans_config(uint8_t uart_index, uint32_t baudrate);
|
||||
int uart_trans_start_all(void);
|
||||
bool uart_trans_send_buffer(uart_channel_t channel, const uint8_t *data, uint16_t len);
|
||||
const char *uart_trans_send_result_to_str(uart_trans_send_result_t result);
|
||||
uart_trans_send_result_t uart_trans_send_buffer(uart_channel_t channel, const uint8_t *data, uint16_t len);
|
||||
void uart_trans_notify_rx_from_isr(uart_channel_t channel, BaseType_t *xHigherPriorityTaskWoken);
|
||||
void uart_trans_tx_cplt_handler(uart_channel_t channel);
|
||||
void UartRxTask(void *argument);
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# TCP2UART 当前交接 Prompt
|
||||
|
||||
## 1. 用途
|
||||
|
||||
本文件不再承担“项目从零编码任务说明”的职责,而是作为**当前工程的交接入口 Prompt**使用。
|
||||
|
||||
长期有效的工程知识、调试经验和现状说明,已经分别固化到其它文档,不再全部堆在本文件中。
|
||||
|
||||
---
|
||||
|
||||
## 2. 接手后先读什么
|
||||
|
||||
请按以下顺序阅读:
|
||||
|
||||
1. `交接清单.md` —— 当前状态、接下来要做什么、怎么做
|
||||
2. `工程调试指南.md` —— 已固化的调试经验与当前工程真实边界
|
||||
3. `项目技术实现.md` —— 架构、任务模型、协议模型
|
||||
4. `项目需求说明.md`
|
||||
5. `AT固件使用手册.md`
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前工程一句话状态
|
||||
|
||||
当前项目已从早期 bring-up 阶段推进到 full-task 运行期调试阶段;`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前在 `STM32F103RCT6` 上的 RAM/heap 余量过低,已被认定为调试噪声的主要来源之一,因此推荐下一阶段先切到 pin2pin 的 `STM32F103RDT6` 再继续分析。
|
||||
|
||||
---
|
||||
|
||||
## 4. 下一位 agent 的当前目标
|
||||
|
||||
请不要把当前任务理解成“立刻继续加逻辑修补”。当前更重要的是:
|
||||
|
||||
1. 完成 `STM32F103RCT6 -> STM32F103RDT6` 目标切换
|
||||
2. 使用真实 Keil 日志重新确认构建成功
|
||||
3. 在新器件上复测当前代码基线
|
||||
4. 比较:
|
||||
- 故障是否消失
|
||||
- 是否明显后移
|
||||
- 是否仍停在相同 enabled path
|
||||
5. 只有拿到新器件上的第一轮 RTT / heap / HWM 证据后,再决定下一步最小化改动
|
||||
|
||||
---
|
||||
|
||||
## 5. 工作约束
|
||||
|
||||
1. 构建真值以 `MDK-ARM/build_capture.txt`、`TCP2UART.build_log.htm`、`.map` 为准
|
||||
2. 不要再把 viewer 当作当前构建真值
|
||||
3. 不要忽视 `DIAG_TASK_ISOLATION=1 正常、=0 异常` 这个前提
|
||||
4. 在新器件的第一轮复测前,避免继续做大范围代码改动
|
||||
5. 每次只做一个能明显改变故障边界的最小改动,并保留 RTT 证据
|
||||
|
||||
---
|
||||
|
||||
## 6. 可直接复制给下一位 agent 的起始 Prompt
|
||||
|
||||
```text
|
||||
请先阅读:`交接清单.md`、`工程调试指南.md`、`项目技术实现.md`。
|
||||
|
||||
当前项目是 STM32F103 + FreeRTOS + lwIP + CH390 的 TCP↔UART 透传工程。此前在 `STM32F103RCT6` 上调试时,`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前最关键的资源事实是:在 `RCT6` 上 full-task 创建完四个 TCP 任务后,FreeRTOS heap 只剩约 944 bytes,静态 RAM 也已逼近物理上限,因此当前推荐先切换到 pin2pin 的 `STM32F103RDT6`,保持现有代码基线基本不变,先完成第一轮换片复测,再根据新器件上的 RTT、free/min heap 和 enabled `S1/C1` 行为决定下一步。
|
||||
|
||||
你的当前目标不是立刻修完所有问题,而是:
|
||||
1. 完成 `RCT6 -> RDT6` 目标切换;
|
||||
2. 用真实 Keil 日志确认构建通过;
|
||||
3. 在新器件上复测当前代码,判断故障是否消失、后移或保持原状;
|
||||
4. 仅在拿到新器件上的第一轮 RTT 后,再继续做最小化的下一步判别。
|
||||
```
|
||||
@@ -47,6 +47,7 @@ void MX_USART2_UART_Init(void);
|
||||
void MX_USART3_UART_Init(void);
|
||||
|
||||
/* USER CODE BEGIN Prototypes */
|
||||
void USART_SetConfiguredBaudrates(uint32_t usart2_baudrate, uint32_t usart3_baudrate);
|
||||
|
||||
/* USER CODE END Prototypes */
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ static TaskHandle_t xTcpCliTaskC1Handle = NULL;
|
||||
static TaskHandle_t xTcpCliTaskC2Handle = NULL;
|
||||
static TaskHandle_t xDefaultTaskHandle = NULL;
|
||||
static BaseType_t xNetworkTasksStarted = pdFALSE;
|
||||
static volatile BaseType_t xNetworkTaskStopRequested = pdFALSE;
|
||||
static volatile BaseType_t xNetworkRestartRequested = pdFALSE;
|
||||
|
||||
void app_start_network_tasks(void)
|
||||
{
|
||||
@@ -49,6 +51,11 @@ void app_start_network_tasks(void)
|
||||
return;
|
||||
}
|
||||
|
||||
if (xNetworkTaskStopRequested != pdFALSE) {
|
||||
debug_log_write("[NET] start-network-tasks stop-pending\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cfg = config_get();
|
||||
|
||||
debug_log_printf("[NET] start-network-tasks enter free=%lu min=%lu\r\n",
|
||||
@@ -107,6 +114,68 @@ void app_start_network_tasks(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_request_network_task_stop(void)
|
||||
{
|
||||
xNetworkTaskStopRequested = pdTRUE;
|
||||
}
|
||||
|
||||
void app_clear_network_task_stop(void)
|
||||
{
|
||||
xNetworkTaskStopRequested = pdFALSE;
|
||||
}
|
||||
|
||||
BaseType_t app_network_task_stop_requested(void)
|
||||
{
|
||||
return xNetworkTaskStopRequested;
|
||||
}
|
||||
|
||||
BaseType_t app_network_tasks_are_stopped(void)
|
||||
{
|
||||
return (xTcpSrvTaskS1Handle == NULL &&
|
||||
xTcpSrvTaskS2Handle == NULL &&
|
||||
xTcpCliTaskC1Handle == NULL &&
|
||||
xTcpCliTaskC2Handle == NULL) ? pdTRUE : pdFALSE;
|
||||
}
|
||||
|
||||
void app_on_network_task_exit(TaskHandle_t task_handle)
|
||||
{
|
||||
taskENTER_CRITICAL();
|
||||
|
||||
if (task_handle == xTcpSrvTaskS1Handle) {
|
||||
xTcpSrvTaskS1Handle = NULL;
|
||||
} else if (task_handle == xTcpSrvTaskS2Handle) {
|
||||
xTcpSrvTaskS2Handle = NULL;
|
||||
} else if (task_handle == xTcpCliTaskC1Handle) {
|
||||
xTcpCliTaskC1Handle = NULL;
|
||||
} else if (task_handle == xTcpCliTaskC2Handle) {
|
||||
xTcpCliTaskC2Handle = NULL;
|
||||
}
|
||||
|
||||
if (xTcpSrvTaskS1Handle == NULL &&
|
||||
xTcpSrvTaskS2Handle == NULL &&
|
||||
xTcpCliTaskC1Handle == NULL &&
|
||||
xTcpCliTaskC2Handle == NULL) {
|
||||
xNetworkTasksStarted = pdFALSE;
|
||||
}
|
||||
|
||||
taskEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void app_request_network_restart(void)
|
||||
{
|
||||
xNetworkRestartRequested = pdTRUE;
|
||||
}
|
||||
|
||||
void app_clear_network_restart_request(void)
|
||||
{
|
||||
xNetworkRestartRequested = pdFALSE;
|
||||
}
|
||||
|
||||
BaseType_t app_network_restart_requested(void)
|
||||
{
|
||||
return xNetworkRestartRequested;
|
||||
}
|
||||
|
||||
static void StartDefaultTask(void *argument)
|
||||
{
|
||||
BaseType_t iwdg_ready = pdFALSE;
|
||||
|
||||
+8
-2
@@ -76,9 +76,15 @@ void MX_GPIO_Init(void)
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
||||
|
||||
/* EXTI interrupt init*/
|
||||
/* EXTI interrupt init
|
||||
* Keep CH390 INT masked during early boot. PB0 may already be asserted at
|
||||
* power-on, while the FreeRTOS semaphore is not created until
|
||||
* MX_FREERTOS_Init(). The network driver enables EXTI0 after CH390 and the
|
||||
* RTOS objects are ready.
|
||||
*/
|
||||
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
|
||||
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
|
||||
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
|
||||
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
||||
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -68,6 +68,7 @@ void MX_FREERTOS_Init(void);
|
||||
/* USER CODE BEGIN PFP */
|
||||
static void CH390_HardwareReset(void);
|
||||
static void LED_Init(void);
|
||||
static void ApplyConfiguredUartBaudrates(void);
|
||||
void Debug_TrapWithRttHint(const char *tag);
|
||||
/* USER CODE END PFP */
|
||||
|
||||
@@ -109,6 +110,12 @@ void LED_Toggle(void)
|
||||
HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
|
||||
}
|
||||
|
||||
static void ApplyConfiguredUartBaudrates(void)
|
||||
{
|
||||
USART_SetConfiguredBaudrates(config_get_uart_baudrate(LINK_UART_U0),
|
||||
config_get_uart_baudrate(LINK_UART_U1));
|
||||
}
|
||||
|
||||
/* USER CODE END 0 */
|
||||
|
||||
/**
|
||||
@@ -143,6 +150,8 @@ int main(void)
|
||||
MX_GPIO_Init();
|
||||
MX_DMA_Init();
|
||||
MX_USART1_UART_Init();
|
||||
config_init();
|
||||
ApplyConfiguredUartBaudrates();
|
||||
MX_USART2_UART_Init();
|
||||
MX_USART3_UART_Init();
|
||||
MX_SPI1_Init();
|
||||
@@ -155,8 +164,6 @@ int main(void)
|
||||
/* CH390 硬件复位 */
|
||||
CH390_HardwareReset();
|
||||
|
||||
/* Initialize configuration from Flash (fallback to defaults on invalid data) */
|
||||
config_init();
|
||||
debug_log_boot("config-ready");
|
||||
|
||||
/* USER CODE END 2 */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "stm32f1xx_it.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
#include "task.h"
|
||||
|
||||
#include "app_runtime.h"
|
||||
@@ -101,8 +102,11 @@ void EXTI0_IRQHandler(void)
|
||||
|
||||
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
|
||||
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
|
||||
xSemaphoreGiveFromISR(xNetSemaphore, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
if ((xNetSemaphore != NULL) &&
|
||||
(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)) {
|
||||
xSemaphoreGiveFromISR(xNetSemaphore, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -22,6 +22,9 @@
|
||||
|
||||
/* USER CODE BEGIN 0 */
|
||||
|
||||
static uint32_t g_usart2_baudrate = 115200u;
|
||||
static uint32_t g_usart3_baudrate = 115200u;
|
||||
|
||||
/* USER CODE END 0 */
|
||||
|
||||
UART_HandleTypeDef huart1;
|
||||
@@ -76,7 +79,7 @@ void MX_USART2_UART_Init(void)
|
||||
|
||||
/* USER CODE END USART2_Init 1 */
|
||||
huart2.Instance = USART2;
|
||||
huart2.Init.BaudRate = 115200;
|
||||
huart2.Init.BaudRate = g_usart2_baudrate;
|
||||
huart2.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
huart2.Init.StopBits = UART_STOPBITS_1;
|
||||
huart2.Init.Parity = UART_PARITY_NONE;
|
||||
@@ -105,7 +108,7 @@ void MX_USART3_UART_Init(void)
|
||||
|
||||
/* USER CODE END USART3_Init 1 */
|
||||
huart3.Instance = USART3;
|
||||
huart3.Init.BaudRate = 115200;
|
||||
huart3.Init.BaudRate = g_usart3_baudrate;
|
||||
huart3.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
huart3.Init.StopBits = UART_STOPBITS_1;
|
||||
huart3.Init.Parity = UART_PARITY_NONE;
|
||||
@@ -396,4 +399,10 @@ void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
|
||||
|
||||
/* USER CODE BEGIN 1 */
|
||||
|
||||
void USART_SetConfiguredBaudrates(uint32_t usart2_baudrate, uint32_t usart3_baudrate)
|
||||
{
|
||||
g_usart2_baudrate = usart2_baudrate;
|
||||
g_usart3_baudrate = usart3_baudrate;
|
||||
}
|
||||
|
||||
/* USER CODE END 1 */
|
||||
|
||||
+100
-6
@@ -12,6 +12,25 @@
|
||||
#include "CH390.h"
|
||||
#include "CH390_Interface.h"
|
||||
|
||||
#define CH390_EPCR_POLL_LIMIT 100000u
|
||||
|
||||
static int ch390_wait_epcr_ready(void)
|
||||
{
|
||||
uint32_t poll_count = CH390_EPCR_POLL_LIMIT;
|
||||
|
||||
while ((ch390_read_reg(CH390_EPCR) & 0x01u) != 0u)
|
||||
{
|
||||
if (poll_count == 0u)
|
||||
{
|
||||
ch390_write_reg(CH390_EPCR, 0x00u);
|
||||
return -1;
|
||||
}
|
||||
--poll_count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ch390_probe_rx_header(uint8_t *head)
|
||||
{
|
||||
if (head == 0)
|
||||
@@ -22,6 +41,46 @@ void ch390_probe_rx_header(uint8_t *head)
|
||||
ch390_read_mem(head, 4);
|
||||
}
|
||||
|
||||
int ch390_peek_packet(uint8_t *rx_status, uint16_t *rx_len)
|
||||
{
|
||||
uint8_t nsr;
|
||||
uint8_t header[4];
|
||||
uint16_t mrr;
|
||||
|
||||
if (rx_status != 0)
|
||||
{
|
||||
*rx_status = 0u;
|
||||
}
|
||||
|
||||
if (rx_len != 0)
|
||||
{
|
||||
*rx_len = 0u;
|
||||
}
|
||||
|
||||
nsr = ch390_read_reg(CH390_NSR);
|
||||
if ((nsr & NSR_RXRDY) == 0u)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
mrr = (uint16_t)ch390_read_mrrl() | ((uint16_t)ch390_read_mrrh() << 8);
|
||||
ch390_read_mem(header, 4);
|
||||
ch390_write_reg(CH390_MRRL, (uint8_t)(mrr & 0xffu));
|
||||
ch390_write_reg(CH390_MRRH, (uint8_t)((mrr >> 8) & 0xffu));
|
||||
|
||||
if (rx_status != 0)
|
||||
{
|
||||
*rx_status = header[1];
|
||||
}
|
||||
|
||||
if (rx_len != 0)
|
||||
{
|
||||
*rx_len = (uint16_t)header[2] | ((uint16_t)header[3] << 8);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ch390_receive_packet
|
||||
* @brief Receive packet
|
||||
@@ -33,6 +92,7 @@ void ch390_probe_rx_header(uint8_t *head)
|
||||
uint32_t ch390_receive_packet(uint8_t *buff, uint8_t *rx_status)
|
||||
{
|
||||
uint8_t nsr;
|
||||
uint8_t ready;
|
||||
uint16_t rx_len = 0;
|
||||
uint8_t ReceiveData[4];
|
||||
|
||||
@@ -48,6 +108,18 @@ uint32_t ch390_receive_packet(uint8_t *buff, uint8_t *rx_status)
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void)ch390_read_mrcmdx();
|
||||
ready = ch390_read_mrcmdx1();
|
||||
if (ready == 0u)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (ready != CH390_PKT_RDY)
|
||||
{
|
||||
ch390_rx_reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ch390_read_mem(ReceiveData, 4);
|
||||
|
||||
if (rx_status != 0)
|
||||
@@ -56,15 +128,17 @@ uint32_t ch390_receive_packet(uint8_t *buff, uint8_t *rx_status)
|
||||
}
|
||||
rx_len = (uint16_t)ReceiveData[2] | ((uint16_t)ReceiveData[3] << 8);
|
||||
|
||||
if (((ReceiveData[1] & 0x3Fu) != 0u) ||
|
||||
(rx_len < 14u) ||
|
||||
if ((ReceiveData[0] != CH390_PKT_RDY) ||
|
||||
((ReceiveData[1] & 0x3Fu) != 0u) ||
|
||||
(rx_len < (uint16_t)(14u + CH390_PKT_CRC_LEN)) ||
|
||||
(rx_len > CH390_PKT_MAX))
|
||||
{
|
||||
ch390_rx_reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ch390_read_mem(buff, rx_len);
|
||||
return rx_len;
|
||||
return (uint32_t)(rx_len - CH390_PKT_CRC_LEN);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +199,17 @@ void ch390_drop_packet(uint16_t len)
|
||||
ch390_write_reg(CH390_MRRH, (mdr >> 8) & 0xff);
|
||||
}
|
||||
|
||||
void ch390_rx_reset(void)
|
||||
{
|
||||
uint8_t rcr = ch390_read_reg(CH390_RCR);
|
||||
|
||||
ch390_write_reg(CH390_RCR, (uint8_t)(rcr & (uint8_t)(~RCR_RXEN)));
|
||||
ch390_write_reg(CH390_MPTRCR, MPTRCR_RST_RX);
|
||||
ch390_write_reg(CH390_NSR, NSR_RXOV);
|
||||
ch390_write_reg(CH390_ISR, (uint8_t)(ISR_ROS | ISR_ROO | ISR_PR));
|
||||
ch390_write_reg(CH390_RCR, (uint8_t)(rcr | RCR_RXEN));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ch390_read_phy
|
||||
* @brief Read PHY register
|
||||
@@ -135,7 +220,10 @@ uint16_t ch390_read_phy(uint8_t reg)
|
||||
ch390_write_reg(CH390_EPAR, CH390_PHY | reg);
|
||||
// Chose PHY, send read command
|
||||
ch390_write_reg(CH390_EPCR, EPCR_ERPRR | EPCR_EPOS);
|
||||
while(ch390_read_reg(CH390_EPCR) & 0x01);
|
||||
if (ch390_wait_epcr_ready() != 0)
|
||||
{
|
||||
return 0xFFFFu;
|
||||
}
|
||||
// Clear read command
|
||||
ch390_write_reg(CH390_EPCR, 0x00);
|
||||
return (ch390_read_reg(CH390_EPDRH) << 8) |
|
||||
@@ -155,7 +243,10 @@ void ch390_write_phy(uint8_t reg, uint16_t value)
|
||||
ch390_write_reg(CH390_EPDRH, ((value >> 8) & 0xff)); // High byte
|
||||
// Chose PHY, send write command
|
||||
ch390_write_reg(CH390_EPCR, 0x0A);
|
||||
while(ch390_read_reg(CH390_EPCR) & 0x01);
|
||||
if (ch390_wait_epcr_ready() != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Clear write command
|
||||
ch390_write_reg(CH390_EPCR, 0x00);
|
||||
}
|
||||
@@ -173,7 +264,10 @@ void ch390_write_eeprom(uint8_t reg, uint16_t value)
|
||||
ch390_write_reg(CH390_EPDRH, ((value >> 8) & 0xff)); // High byte
|
||||
// Chose EEPROM, send write command
|
||||
ch390_write_reg(CH390_EPCR, EPCR_ERPRW);
|
||||
while(ch390_read_reg(CH390_EPCR) & 0x01);
|
||||
if (ch390_wait_epcr_ready() != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Clear write command
|
||||
ch390_write_reg(CH390_EPCR, 0x00);
|
||||
}
|
||||
|
||||
@@ -150,6 +150,8 @@ enum ch390_phy_mode
|
||||
#define CH390_BCASTCR 0x53
|
||||
#define CH390_INTCKCR 0x54
|
||||
#define CH390_MPTRCR 0x55
|
||||
#define MPTRCR_RST_TX (1<<1)
|
||||
#define MPTRCR_RST_RX (1<<0)
|
||||
#define CH390_MLEDCR 0x57
|
||||
#define CH390_MRCMDX 0x70
|
||||
#define CH390_MRCMDX1 0x71
|
||||
@@ -302,6 +304,8 @@ enum ch390_phy_mode
|
||||
#define CH390_RLENCR 0x52
|
||||
#define CH390_BCASTCR 0x53
|
||||
#define CH390_MPTRCR 0x55
|
||||
#define MPTRCR_RST_TX (1<<1)
|
||||
#define MPTRCR_RST_RX (1<<0)
|
||||
#define CH390_MRCMDX 0xF0
|
||||
#define CH390_MRCMDX1 0xF1
|
||||
#define CH390_MRCMD 0xF2
|
||||
@@ -356,6 +360,7 @@ enum ch390_phy_mode
|
||||
#define CH390_PKT_NONE 0x00 /* No packet received */
|
||||
#define CH390_PKT_RDY 0x01 /* Packet ready to receive */
|
||||
#define CH390_PKT_ERR 0xFE /* Un-stable states */
|
||||
#define CH390_PKT_CRC_LEN 4u /* Ethernet FCS stored in RX SRAM */
|
||||
#define CH390_PKT_MAX 1536 /* Received packet max size */
|
||||
#define CH390_PKT_MIN 64
|
||||
|
||||
@@ -657,6 +662,21 @@ int ch390_runtime_link_up_from_status(const struct ch390_runtime_status *status)
|
||||
*/
|
||||
void ch390_probe_rx_header(uint8_t *head);
|
||||
|
||||
/**
|
||||
* @name ch390_peek_packet
|
||||
* @brief Peek current RX header without consuming the packet.
|
||||
* @param rx_status - Output abnormal status while receiving packet
|
||||
* @param rx_len - Output packet length from RX header
|
||||
* @return 0: no packet pending 1: header sampled
|
||||
*/
|
||||
int ch390_peek_packet(uint8_t *rx_status, uint16_t *rx_len);
|
||||
|
||||
/**
|
||||
* @name ch390_rx_reset
|
||||
* @brief Repair RX datapath after overflow/corruption without full chip reset.
|
||||
*/
|
||||
void ch390_rx_reset(void);
|
||||
|
||||
/**
|
||||
* @name ch390_runtime_receive_packet
|
||||
* @brief Runtime RX entry point for packet receive
|
||||
|
||||
@@ -188,6 +188,8 @@ void ch390_interrupt_init(void)
|
||||
/* EXTI0 is configured in CubeMX for PB0 */
|
||||
/* NVIC priority should be >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY */
|
||||
/* for FreeRTOS compatibility */
|
||||
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
|
||||
__HAL_GPIO_EXTI_CLEAR_IT(CH390_INT_PIN);
|
||||
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
|
||||
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
||||
}
|
||||
@@ -283,25 +285,16 @@ uint8_t ch390_read_reg(uint8_t reg)
|
||||
|
||||
static uint8_t ch390_read_rx_reg(uint8_t reg)
|
||||
{
|
||||
uint8_t tx_buf[3];
|
||||
uint8_t rx_buf[3];
|
||||
|
||||
tx_buf[0] = OPC_MEM_DMY_R;
|
||||
tx_buf[1] = reg;
|
||||
tx_buf[2] = 0x00u;
|
||||
uint8_t value;
|
||||
|
||||
CH390_SPI_ATOMIC_ENTER();
|
||||
ch390_cs(0);
|
||||
if (HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 3, SPI_TIMEOUT) != HAL_OK)
|
||||
{
|
||||
ch390_cs(1);
|
||||
CH390_SPI_ATOMIC_EXIT();
|
||||
return 0u;
|
||||
}
|
||||
ch390_spi_exchange_byte(reg | OPC_REG_R);
|
||||
value = ch390_spi_dummy_read();
|
||||
ch390_cs(1);
|
||||
CH390_SPI_ATOMIC_EXIT();
|
||||
|
||||
return rx_buf[2];
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t ch390_read_mrcmdx(void)
|
||||
|
||||
@@ -30,28 +30,28 @@ uint8_t ch390_read_reg(uint8_t reg);
|
||||
|
||||
/**
|
||||
* @name ch390_read_mrcmdx
|
||||
* @brief Read MRCMDX via memory-dummy-read opcode
|
||||
* @brief Read MRCMDX receive-ready latch
|
||||
* @return Register value
|
||||
*/
|
||||
uint8_t ch390_read_mrcmdx(void);
|
||||
|
||||
/**
|
||||
* @name ch390_read_mrcmdx1
|
||||
* @brief Read MRCMDX1 via memory-dummy-read opcode
|
||||
* @brief Read MRCMDX1 receive-ready latch
|
||||
* @return Register value
|
||||
*/
|
||||
uint8_t ch390_read_mrcmdx1(void);
|
||||
|
||||
/**
|
||||
* @name ch390_read_mrrl
|
||||
* @brief Read MRRL via memory-dummy-read opcode
|
||||
* @brief Read MRRL receive memory pointer register
|
||||
* @return Register value
|
||||
*/
|
||||
uint8_t ch390_read_mrrl(void);
|
||||
|
||||
/**
|
||||
* @name ch390_read_mrrh
|
||||
* @brief Read MRRH via memory-dummy-read opcode
|
||||
* @brief Read MRRH receive memory pointer register
|
||||
* @return Register value
|
||||
*/
|
||||
uint8_t ch390_read_mrrh(void);
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
*
|
||||
* Key design decisions:
|
||||
* - netconn API for thread-safe multi-connection TCP
|
||||
* - tcpip_thread handles all lwIP core operations
|
||||
* - LWIP_TCPIP_CORE_LOCKING=1 allows direct send from application tasks
|
||||
* - tcpip_thread handles netconn API and timers
|
||||
* - core locking lets the poll task process RX packets synchronously
|
||||
* - Conservative memory footprint: target ~16KB for lwIP
|
||||
*/
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
#define LWIP_NETCONN 1
|
||||
#define LWIP_NETIF_API 1
|
||||
|
||||
/* Core locking: allows netconn_write/recv from any task without going through mbox */
|
||||
/* Core locking: process netif RX synchronously instead of consuming TCPIP_MSG_INPKT slots. */
|
||||
#define LWIP_TCPIP_CORE_LOCKING 1
|
||||
#define LWIP_TCPIP_CORE_LOCKING_INPUT 0
|
||||
#define LWIP_TCPIP_CORE_LOCKING_INPUT 1
|
||||
|
||||
/* Critical section protection */
|
||||
#define SYS_LIGHTWEIGHT_PROT 1
|
||||
@@ -54,7 +54,7 @@
|
||||
#define MEM_SIZE (7 * 1024)
|
||||
|
||||
/* Number of pbufs in pool.
|
||||
* 10 pools for 4 concurrent connections with some headroom. */
|
||||
* RX is processed synchronously under the core lock, so a small pool is sufficient. */
|
||||
#define PBUF_POOL_SIZE 8
|
||||
|
||||
/* Size of each pbuf in pool (must hold one Ethernet frame) */
|
||||
|
||||
@@ -23,7 +23,23 @@ struct ethernetif {
|
||||
err_t ethernetif_init(struct netif *netif);
|
||||
void ethernetif_input(struct netif *netif);
|
||||
void lwip_netif_init(const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw);
|
||||
void ethernetif_diag_ch390_init(void);
|
||||
void ethernetif_diag_poll_status(void);
|
||||
void ethernetif_check_link(void);
|
||||
void ethernetif_apply_runtime_netif_config(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac);
|
||||
void ethernetif_force_full_recovery(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac);
|
||||
void ethernetif_force_link_down(void);
|
||||
uint16_t ethernetif_ch390_get_vendor_id(void);
|
||||
uint16_t ethernetif_ch390_get_product_id(void);
|
||||
uint8_t ethernetif_ch390_get_revision(void);
|
||||
uint8_t ethernetif_ch390_health_ok(void);
|
||||
uint8_t ethernetif_get_effective_mac(uint8_t *mac);
|
||||
uint8_t ethernetif_link_is_up(void);
|
||||
void ethernetif_poll(void);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ err_t ethernet_output(struct netif *netif,
|
||||
{
|
||||
struct pbuf *q;
|
||||
struct eth_hdr *ethhdr;
|
||||
err_t err;
|
||||
|
||||
LWIP_ASSERT("netif != NULL", netif != NULL);
|
||||
LWIP_ASSERT("p != NULL", p != NULL);
|
||||
@@ -56,7 +57,9 @@ err_t ethernet_output(struct netif *netif,
|
||||
SMEMCPY(ðhdr->src, src, sizeof(struct eth_addr));
|
||||
ethhdr->type = lwip_htons(eth_type);
|
||||
|
||||
return netif->linkoutput(netif, q);
|
||||
err = netif->linkoutput(netif, q);
|
||||
pbuf_free(q);
|
||||
return err;
|
||||
}
|
||||
|
||||
err_t ethernet_input(struct pbuf *p, struct netif *netif)
|
||||
|
||||
@@ -41,12 +41,59 @@ static SemaphoreHandle_t spi_mutex = NULL;
|
||||
static uint8_t s_rx_buffer[CH390_PKT_MAX];
|
||||
static uint8_t s_tx_buffer[CH390_PKT_MAX];
|
||||
static uint8_t s_garp_sent = 0u;
|
||||
static uint8_t s_effective_mac[ETHARP_HWADDR_LEN];
|
||||
static uint8_t s_effective_mac_valid = 0u;
|
||||
|
||||
#define CH390_TX_TIMEOUT_RESTART_THRESHOLD 6u
|
||||
#define CH390_EXPECTED_VENDOR_ID 0x1C00u
|
||||
#define CH390_EXPECTED_PRODUCT_ID 0x9151u
|
||||
#define ETH_RX_LOG_INITIAL_COUNT 3u
|
||||
#define ETH_RX_LOG_EVERY_COUNT 64u
|
||||
|
||||
#define ETH_RECOVERY_NONE 0u
|
||||
#define ETH_RECOVERY_RX 1u
|
||||
#define ETH_RECOVERY_FULL 2u
|
||||
|
||||
struct ethernetif_recovery_state
|
||||
{
|
||||
uint8_t pending;
|
||||
uint8_t in_progress;
|
||||
uint8_t tx_timeout_streak;
|
||||
uint32_t tx_timeout_count;
|
||||
uint32_t rx_overflow_count;
|
||||
uint32_t rx_bad_count;
|
||||
uint32_t rx_recovery_count;
|
||||
uint32_t full_recovery_count;
|
||||
};
|
||||
|
||||
static struct ethernetif_recovery_state s_recovery = {0};
|
||||
|
||||
/* Forward declarations */
|
||||
static err_t low_level_init(struct netif *netif);
|
||||
static err_t low_level_output(struct netif *netif, struct pbuf *p);
|
||||
static struct pbuf *low_level_input(struct netif *netif);
|
||||
static void ethernetif_update_link(uint8_t link_status);
|
||||
static uint32_t ethernetif_record_counter(uint32_t *counter);
|
||||
static uint32_t ethernetif_record_event_and_request(uint32_t *counter, uint8_t recovery_type);
|
||||
static uint8_t ethernetif_claim_recovery(void);
|
||||
static void ethernetif_finish_recovery(void);
|
||||
static uint8_t ethernetif_run_pending_recovery(void);
|
||||
static uint8_t ethernetif_should_log_counter(uint32_t count);
|
||||
static void ethernetif_clear_tx_timeout_streak(void);
|
||||
static uint32_t ethernetif_note_tx_timeout_streak(void);
|
||||
static uint8_t ethernetif_mac_is_all_zero(const uint8_t *mac);
|
||||
static void ethernetif_store_effective_mac(const uint8_t *mac);
|
||||
static void ethernetif_apply_configured_or_internal_mac_locked(const uint8_t *configured_mac);
|
||||
static void ethernetif_prepare_runtime_netif_locked(struct netif *netif);
|
||||
static void ethernetif_apply_runtime_netif_config_locked(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac);
|
||||
static void ethernetif_perform_full_recovery(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac,
|
||||
uint8_t *link_status);
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Low Level Hardware Functions
|
||||
@@ -58,7 +105,6 @@ static void ethernetif_update_link(uint8_t link_status);
|
||||
*/
|
||||
static err_t low_level_init(struct netif *netif)
|
||||
{
|
||||
struct ethernetif *ethernetif = netif->state;
|
||||
const device_config_t *cfg = config_get();
|
||||
|
||||
/* Create SPI mutex */
|
||||
@@ -88,24 +134,9 @@ static err_t low_level_init(struct netif *netif)
|
||||
ch390_read_reg(CH390_BCASTCR),
|
||||
ch390_read_reg(CH390_MAR + 7));
|
||||
|
||||
/* Apply configured MAC address to CH390 before reading it back into lwIP */
|
||||
ch390_set_mac_address((uint8_t *)cfg->net.mac);
|
||||
ethernetif_apply_configured_or_internal_mac_locked(cfg->net.mac);
|
||||
|
||||
/* Set MAC hardware address length */
|
||||
netif->hwaddr_len = ETHARP_HWADDR_LEN;
|
||||
|
||||
/* Get MAC address from CH390 */
|
||||
ch390_get_mac(netif->hwaddr);
|
||||
|
||||
/* Maximum transfer unit */
|
||||
netif->mtu = 1500;
|
||||
|
||||
/* Device capabilities */
|
||||
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET;
|
||||
|
||||
/* Initialize state */
|
||||
ethernetif->rx_len = 0;
|
||||
ethernetif->rx_status = 0;
|
||||
ethernetif_prepare_runtime_netif_locked(netif);
|
||||
|
||||
/* Enable CH390 interrupt */
|
||||
ch390_interrupt_init();
|
||||
@@ -113,6 +144,408 @@ static err_t low_level_init(struct netif *netif)
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static uint32_t ethernetif_record_counter(uint32_t *counter)
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
taskENTER_CRITICAL();
|
||||
++(*counter);
|
||||
value = *counter;
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint32_t ethernetif_record_event_and_request(uint32_t *counter, uint8_t recovery_type)
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
taskENTER_CRITICAL();
|
||||
++(*counter);
|
||||
if (recovery_type == ETH_RECOVERY_FULL)
|
||||
{
|
||||
app_request_network_restart();
|
||||
}
|
||||
else if (recovery_type > s_recovery.pending)
|
||||
{
|
||||
s_recovery.pending = recovery_type;
|
||||
}
|
||||
value = *counter;
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void ethernetif_clear_tx_timeout_streak(void)
|
||||
{
|
||||
taskENTER_CRITICAL();
|
||||
s_recovery.tx_timeout_streak = 0u;
|
||||
taskEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
static uint32_t ethernetif_note_tx_timeout_streak(void)
|
||||
{
|
||||
uint32_t streak;
|
||||
|
||||
taskENTER_CRITICAL();
|
||||
++s_recovery.tx_timeout_count;
|
||||
if (s_recovery.tx_timeout_streak < 0xFFu)
|
||||
{
|
||||
++s_recovery.tx_timeout_streak;
|
||||
}
|
||||
streak = s_recovery.tx_timeout_streak;
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
return streak;
|
||||
}
|
||||
|
||||
static uint8_t ethernetif_claim_recovery(void)
|
||||
{
|
||||
uint8_t recovery_type = ETH_RECOVERY_NONE;
|
||||
|
||||
taskENTER_CRITICAL();
|
||||
if ((s_recovery.in_progress == 0u) && (s_recovery.pending != ETH_RECOVERY_NONE))
|
||||
{
|
||||
recovery_type = s_recovery.pending;
|
||||
s_recovery.pending = ETH_RECOVERY_NONE;
|
||||
s_recovery.in_progress = 1u;
|
||||
}
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
return recovery_type;
|
||||
}
|
||||
|
||||
static void ethernetif_finish_recovery(void)
|
||||
{
|
||||
taskENTER_CRITICAL();
|
||||
s_recovery.in_progress = 0u;
|
||||
taskEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
static uint8_t ethernetif_should_log_counter(uint32_t count)
|
||||
{
|
||||
if (count <= ETH_RX_LOG_INITIAL_COUNT)
|
||||
{
|
||||
return 1u;
|
||||
}
|
||||
|
||||
return ((count % ETH_RX_LOG_EVERY_COUNT) == 0u) ? 1u : 0u;
|
||||
}
|
||||
|
||||
static uint8_t ethernetif_run_pending_recovery(void)
|
||||
{
|
||||
ip4_addr_t ipaddr;
|
||||
ip4_addr_t netmask;
|
||||
ip4_addr_t gateway;
|
||||
uint8_t recovery_type;
|
||||
uint8_t link_status = 0u;
|
||||
uint32_t count;
|
||||
|
||||
recovery_type = ethernetif_claim_recovery();
|
||||
if (recovery_type == ETH_RECOVERY_NONE)
|
||||
{
|
||||
return ETH_RECOVERY_NONE;
|
||||
}
|
||||
|
||||
if (recovery_type == ETH_RECOVERY_FULL)
|
||||
{
|
||||
const device_config_t *cfg = config_get();
|
||||
|
||||
IP4_ADDR(&ipaddr, cfg->net.ip[0], cfg->net.ip[1], cfg->net.ip[2], cfg->net.ip[3]);
|
||||
IP4_ADDR(&netmask, cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3]);
|
||||
IP4_ADDR(&gateway, cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3]);
|
||||
ethernetif_clear_tx_timeout_streak();
|
||||
ethernetif_perform_full_recovery(&ipaddr, &netmask, &gateway, cfg->net.mac, &link_status);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreTake(spi_mutex, portMAX_DELAY);
|
||||
}
|
||||
ch390_rx_reset();
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreGive(spi_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
if (recovery_type == ETH_RECOVERY_FULL)
|
||||
{
|
||||
count = ethernetif_record_counter(&s_recovery.full_recovery_count);
|
||||
debug_log_printf("[ETH] rec full n=%lu link=%u\r\n",
|
||||
(unsigned long)count,
|
||||
(unsigned int)link_status);
|
||||
}
|
||||
else
|
||||
{
|
||||
count = ethernetif_record_counter(&s_recovery.rx_recovery_count);
|
||||
if (ethernetif_should_log_counter(count) != 0u)
|
||||
{
|
||||
debug_log_printf("[ETH] rec rx n=%lu\r\n", (unsigned long)count);
|
||||
}
|
||||
}
|
||||
|
||||
ethernetif_finish_recovery();
|
||||
return recovery_type;
|
||||
}
|
||||
|
||||
static void ethernetif_perform_full_recovery(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac,
|
||||
uint8_t *link_status)
|
||||
{
|
||||
uint8_t local_link_status = 0u;
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreTake(spi_mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
ch390_spi_init();
|
||||
ch390_hardware_reset();
|
||||
ch390_default_config();
|
||||
ethernetif_apply_configured_or_internal_mac_locked(mac);
|
||||
ch390_set_phy_mode(CH390_AUTO);
|
||||
ch390_rx_enable(1);
|
||||
ethernetif_prepare_runtime_netif_locked(&ch390_netif);
|
||||
ethernetif_apply_runtime_netif_config_locked(ipaddr,
|
||||
netmask,
|
||||
gw,
|
||||
(s_effective_mac_valid != 0u) ? s_effective_mac : NULL);
|
||||
local_link_status = (uint8_t)ch390_get_link_status();
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreGive(spi_mutex);
|
||||
}
|
||||
|
||||
ch390_interrupt_init();
|
||||
|
||||
if (link_status != NULL)
|
||||
{
|
||||
*link_status = local_link_status;
|
||||
}
|
||||
|
||||
ethernetif_update_link(local_link_status);
|
||||
}
|
||||
|
||||
static uint8_t ethernetif_mac_is_all_zero(const uint8_t *mac)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (mac == NULL)
|
||||
{
|
||||
return 1u;
|
||||
}
|
||||
|
||||
for (i = 0u; i < ETHARP_HWADDR_LEN; ++i)
|
||||
{
|
||||
if (mac[i] != 0u)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
return 1u;
|
||||
}
|
||||
|
||||
static void ethernetif_store_effective_mac(const uint8_t *mac)
|
||||
{
|
||||
if (mac == NULL)
|
||||
{
|
||||
s_effective_mac_valid = 0u;
|
||||
return;
|
||||
}
|
||||
|
||||
MEMCPY(s_effective_mac, mac, ETHARP_HWADDR_LEN);
|
||||
s_effective_mac_valid = 1u;
|
||||
}
|
||||
|
||||
static void ethernetif_apply_configured_or_internal_mac_locked(const uint8_t *configured_mac)
|
||||
{
|
||||
uint8_t runtime_mac[ETHARP_HWADDR_LEN];
|
||||
|
||||
if (ethernetif_mac_is_all_zero(configured_mac) == 0u)
|
||||
{
|
||||
ch390_set_mac_address((uint8_t *)configured_mac);
|
||||
ethernetif_store_effective_mac(configured_mac);
|
||||
return;
|
||||
}
|
||||
|
||||
ch390_get_mac(runtime_mac);
|
||||
ethernetif_store_effective_mac(runtime_mac);
|
||||
debug_log_printf("[ETH] use internal mac=%02X:%02X:%02X:%02X:%02X:%02X\r\n",
|
||||
(unsigned int)runtime_mac[0],
|
||||
(unsigned int)runtime_mac[1],
|
||||
(unsigned int)runtime_mac[2],
|
||||
(unsigned int)runtime_mac[3],
|
||||
(unsigned int)runtime_mac[4],
|
||||
(unsigned int)runtime_mac[5]);
|
||||
}
|
||||
|
||||
static void ethernetif_prepare_runtime_netif_locked(struct netif *netif)
|
||||
{
|
||||
struct ethernetif *ethernetif;
|
||||
|
||||
if (netif == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
netif->hwaddr_len = ETHARP_HWADDR_LEN;
|
||||
ch390_get_mac(netif->hwaddr);
|
||||
ethernetif_store_effective_mac(netif->hwaddr);
|
||||
netif->mtu = 1500;
|
||||
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET;
|
||||
|
||||
ethernetif = (struct ethernetif *)netif->state;
|
||||
if (ethernetif != NULL)
|
||||
{
|
||||
ethernetif->rx_len = 0u;
|
||||
ethernetif->rx_status = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
static void ethernetif_apply_runtime_netif_config_locked(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac)
|
||||
{
|
||||
err_t err;
|
||||
|
||||
if (mac != NULL)
|
||||
{
|
||||
MEMCPY(ch390_netif.hwaddr, mac, ETHARP_HWADDR_LEN);
|
||||
s_garp_sent = 0u;
|
||||
}
|
||||
|
||||
if (ipaddr != NULL && netmask != NULL && gw != NULL)
|
||||
{
|
||||
err = netifapi_netif_set_addr(&ch390_netif, ipaddr, netmask, gw);
|
||||
if (err != ERR_OK)
|
||||
{
|
||||
debug_log_printf("[ETH] set-addr fail err=%d\r\n", (int)err);
|
||||
}
|
||||
}
|
||||
|
||||
err = netifapi_netif_set_default(&ch390_netif);
|
||||
if (err != ERR_OK)
|
||||
{
|
||||
debug_log_printf("[ETH] resync-default fail err=%d\r\n", (int)err);
|
||||
}
|
||||
|
||||
err = netifapi_netif_set_up(&ch390_netif);
|
||||
if (err != ERR_OK)
|
||||
{
|
||||
debug_log_printf("[ETH] resync-up fail err=%d\r\n", (int)err);
|
||||
}
|
||||
}
|
||||
|
||||
void ethernetif_apply_runtime_netif_config(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac)
|
||||
{
|
||||
ethernetif_apply_runtime_netif_config_locked(ipaddr, netmask, gw, mac);
|
||||
}
|
||||
|
||||
void ethernetif_force_full_recovery(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac)
|
||||
{
|
||||
uint8_t link_status = 0u;
|
||||
uint32_t count;
|
||||
|
||||
ethernetif_clear_tx_timeout_streak();
|
||||
ethernetif_perform_full_recovery(ipaddr, netmask, gw, mac, &link_status);
|
||||
count = ethernetif_record_counter(&s_recovery.full_recovery_count);
|
||||
debug_log_printf("[ETH] rec full n=%lu link=%u\r\n",
|
||||
(unsigned long)count,
|
||||
(unsigned int)link_status);
|
||||
}
|
||||
|
||||
void ethernetif_force_link_down(void)
|
||||
{
|
||||
ethernetif_update_link(0u);
|
||||
}
|
||||
|
||||
uint8_t ethernetif_ch390_health_ok(void)
|
||||
{
|
||||
return ((ethernetif_ch390_get_vendor_id() == CH390_EXPECTED_VENDOR_ID) &&
|
||||
(ethernetif_ch390_get_product_id() == CH390_EXPECTED_PRODUCT_ID)) ? 1u : 0u;
|
||||
}
|
||||
|
||||
uint8_t ethernetif_get_effective_mac(uint8_t *mac)
|
||||
{
|
||||
if ((mac == NULL) || (s_effective_mac_valid == 0u))
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
MEMCPY(mac, s_effective_mac, ETHARP_HWADDR_LEN);
|
||||
return 1u;
|
||||
}
|
||||
|
||||
uint16_t ethernetif_ch390_get_vendor_id(void)
|
||||
{
|
||||
uint16_t vendor_id;
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreTake(spi_mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
vendor_id = ch390_get_vendor_id();
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreGive(spi_mutex);
|
||||
}
|
||||
|
||||
return vendor_id;
|
||||
}
|
||||
|
||||
uint16_t ethernetif_ch390_get_product_id(void)
|
||||
{
|
||||
uint16_t product_id;
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreTake(spi_mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
product_id = ch390_get_product_id();
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreGive(spi_mutex);
|
||||
}
|
||||
|
||||
return product_id;
|
||||
}
|
||||
|
||||
uint8_t ethernetif_ch390_get_revision(void)
|
||||
{
|
||||
uint8_t revision;
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreTake(spi_mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
revision = ch390_get_revision();
|
||||
|
||||
if (spi_mutex != NULL)
|
||||
{
|
||||
xSemaphoreGive(spi_mutex);
|
||||
}
|
||||
|
||||
return revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transmit a packet via CH390
|
||||
* @param netif Network interface structure
|
||||
@@ -125,6 +558,7 @@ static err_t low_level_output(struct netif *netif, struct pbuf *p)
|
||||
uint16_t offset;
|
||||
uint16_t tx_len;
|
||||
int tx_rc;
|
||||
uint32_t tx_timeout_streak;
|
||||
(void)netif;
|
||||
|
||||
/* Take SPI mutex */
|
||||
@@ -179,10 +613,18 @@ static err_t low_level_output(struct netif *netif, struct pbuf *p)
|
||||
{
|
||||
LINK_STATS_INC(link.drop);
|
||||
LINK_STATS_INC(link.err);
|
||||
debug_log_write("[ETH] tx timeout\r\n");
|
||||
tx_timeout_streak = ethernetif_note_tx_timeout_streak();
|
||||
debug_log_printf("[ETH] tx timeout n=%lu streak=%lu\r\n",
|
||||
(unsigned long)s_recovery.tx_timeout_count,
|
||||
(unsigned long)tx_timeout_streak);
|
||||
if (tx_timeout_streak >= CH390_TX_TIMEOUT_RESTART_THRESHOLD)
|
||||
{
|
||||
app_request_network_restart();
|
||||
}
|
||||
return ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
|
||||
ethernetif_clear_tx_timeout_streak();
|
||||
LINK_STATS_INC(link.xmit);
|
||||
|
||||
return ERR_OK;
|
||||
@@ -266,7 +708,6 @@ static struct pbuf *low_level_input(struct netif *netif)
|
||||
else
|
||||
{
|
||||
/* No memory - drop packet */
|
||||
ch390_drop_packet(ethernetif->rx_len);
|
||||
LINK_STATS_INC(link.memerr);
|
||||
LINK_STATS_INC(link.drop);
|
||||
}
|
||||
@@ -428,7 +869,7 @@ void ethernetif_diag_ch390_init(void)
|
||||
ch390_spi_init();
|
||||
ch390_hardware_reset();
|
||||
ch390_default_config();
|
||||
ch390_set_mac_address((uint8_t *)cfg->net.mac);
|
||||
ethernetif_apply_configured_or_internal_mac_locked(cfg->net.mac);
|
||||
ch390_interrupt_init();
|
||||
|
||||
g_netif_init_ok = 1;
|
||||
@@ -457,8 +898,12 @@ void ethernetif_diag_poll_status(void)
|
||||
*/
|
||||
static void ethernetif_update_link(uint8_t link_status)
|
||||
{
|
||||
uint8_t old_link_status;
|
||||
|
||||
LOCK_TCPIP_CORE();
|
||||
|
||||
old_link_status = netif_is_link_up(&ch390_netif) ? 1u : 0u;
|
||||
|
||||
if (link_status)
|
||||
{
|
||||
if (!netif_is_link_up(&ch390_netif))
|
||||
@@ -481,6 +926,22 @@ static void ethernetif_update_link(uint8_t link_status)
|
||||
}
|
||||
|
||||
UNLOCK_TCPIP_CORE();
|
||||
|
||||
if (old_link_status != ((link_status != 0u) ? 1u : 0u))
|
||||
{
|
||||
debug_log_printf("[ETH] link %s ip=%u.%u.%u.%u mac=%02X:%02X:%02X:%02X:%02X:%02X\r\n",
|
||||
(link_status != 0u) ? "up" : "down",
|
||||
(unsigned int)ip4_addr1_16(netif_ip4_addr(&ch390_netif)),
|
||||
(unsigned int)ip4_addr2_16(netif_ip4_addr(&ch390_netif)),
|
||||
(unsigned int)ip4_addr3_16(netif_ip4_addr(&ch390_netif)),
|
||||
(unsigned int)ip4_addr4_16(netif_ip4_addr(&ch390_netif)),
|
||||
(unsigned int)ch390_netif.hwaddr[0],
|
||||
(unsigned int)ch390_netif.hwaddr[1],
|
||||
(unsigned int)ch390_netif.hwaddr[2],
|
||||
(unsigned int)ch390_netif.hwaddr[3],
|
||||
(unsigned int)ch390_netif.hwaddr[4],
|
||||
(unsigned int)ch390_netif.hwaddr[5]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,6 +952,8 @@ void ethernetif_poll(void)
|
||||
struct ch390_runtime_status runtime_status;
|
||||
struct pbuf *p;
|
||||
err_t input_err;
|
||||
uint32_t rx_overflow_count;
|
||||
uint8_t recovery_type;
|
||||
uint8_t rx_budget = 4u;
|
||||
|
||||
/* Take SPI mutex */
|
||||
@@ -508,19 +971,25 @@ void ethernetif_poll(void)
|
||||
xSemaphoreGive(spi_mutex);
|
||||
}
|
||||
|
||||
/* Handle link change */
|
||||
if ((runtime_status.int_status & ISR_LNKCHG) != 0u)
|
||||
/* Handle RX overflow */
|
||||
if ((runtime_status.int_status & (ISR_ROS | ISR_ROO)) != 0u)
|
||||
{
|
||||
LINK_STATS_INC(link.err);
|
||||
rx_overflow_count = ethernetif_record_event_and_request(&s_recovery.rx_overflow_count, ETH_RECOVERY_RX);
|
||||
if (ethernetif_should_log_counter(rx_overflow_count) != 0u)
|
||||
{
|
||||
debug_log_printf("[ETH] rx ovf n=%lu\r\n", (unsigned long)rx_overflow_count);
|
||||
}
|
||||
}
|
||||
|
||||
recovery_type = ethernetif_run_pending_recovery();
|
||||
|
||||
/* Handle link change unless a full recovery already resynchronized link state. */
|
||||
if ((recovery_type != ETH_RECOVERY_FULL) && ((runtime_status.int_status & ISR_LNKCHG) != 0u))
|
||||
{
|
||||
ethernetif_update_link((uint8_t)ch390_runtime_link_up_from_status(&runtime_status));
|
||||
}
|
||||
|
||||
/* Handle RX overflow */
|
||||
if ((runtime_status.int_status & ISR_ROS) != 0u)
|
||||
{
|
||||
/* RX overflow - packets might be corrupted */
|
||||
LINK_STATS_INC(link.err);
|
||||
}
|
||||
|
||||
/* Always attempt to drain RX FIFO so packet handling does not depend only on ISR_PR timing. */
|
||||
while (rx_budget > 0u)
|
||||
{
|
||||
@@ -539,6 +1008,8 @@ void ethernetif_poll(void)
|
||||
--rx_budget;
|
||||
}
|
||||
|
||||
(void)ethernetif_run_pending_recovery();
|
||||
|
||||
if (rx_budget == 0u)
|
||||
{
|
||||
taskYIELD();
|
||||
|
||||
@@ -62,6 +62,20 @@ void ethernetif_diag_poll_status(void);
|
||||
* @note Call this from the LwIP task periodically or on interrupt
|
||||
*/
|
||||
void ethernetif_check_link(void);
|
||||
void ethernetif_apply_runtime_netif_config(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac);
|
||||
void ethernetif_force_full_recovery(const ip4_addr_t *ipaddr,
|
||||
const ip4_addr_t *netmask,
|
||||
const ip4_addr_t *gw,
|
||||
const uint8_t *mac);
|
||||
void ethernetif_force_link_down(void);
|
||||
uint16_t ethernetif_ch390_get_vendor_id(void);
|
||||
uint16_t ethernetif_ch390_get_product_id(void);
|
||||
uint8_t ethernetif_ch390_get_revision(void);
|
||||
uint8_t ethernetif_ch390_health_ok(void);
|
||||
uint8_t ethernetif_get_effective_mac(uint8_t *mac);
|
||||
|
||||
/**
|
||||
* @brief Query whether physical Ethernet link is currently up
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# TCP2UART 项目说明
|
||||
|
||||
`TCP2UART` 是一个基于 `STM32F103RCT6 + CH390D` 的 TCP 与双串口透传固件。系统运行在 `FreeRTOS` 上,网络协议栈使用 `lwIP NO_SYS=0 + netconn API`,通过 `SEGGER RTT` 输出调试日志,工程入口为 Keil MDK 项目 `MDK-ARM/TCP2UART.uvprojx`。
|
||||
|
||||
本仓库当前文档已经收敛为两类:
|
||||
|
||||
1. 面向开发者的代码阅读与系统理解文档。
|
||||
2. 面向上位机、测试和联调人员的 AT 命令接口文档。
|
||||
|
||||
## 硬件与软件基线
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 主控 | `STM32F103RCT6` |
|
||||
| 以太网芯片 | `CH390D` |
|
||||
| 配置串口 | `USART1` |
|
||||
| 数据串口 | `USART2`、`USART3` |
|
||||
| RTOS | `FreeRTOS` |
|
||||
| TCP/IP | `lwIP`,`NO_SYS=0`,启用 `netconn` API |
|
||||
| 调试输出 | `SEGGER RTT` |
|
||||
| 构建工程 | `MDK-ARM/TCP2UART.uvprojx` |
|
||||
|
||||
`USART1` 只承担 AT 配置面。`USART2/USART3` 是业务数据口,可工作在普通透传模式或 MUX 帧模式。网络侧提供 2 路 TCP Server 实例和 2 路 TCP Client 实例,统一使用 `LINK` 配置模型管理。
|
||||
|
||||
## 当前文档地图
|
||||
|
||||
| 文档 | 读者 | 内容 |
|
||||
|------|------|------|
|
||||
| `README.md` | 所有人 | 项目总览、文档入口、快速理解路径 |
|
||||
| `项目代码阅读指南.md` | 固件开发、调试、交接人员 | 代码目录、启动流程、任务模型、数据流、阅读顺序、调试线索 |
|
||||
| `AT固件使用手册.md` | 上位机、测试、联调人员 | AT 命令、参数模型、默认值、配置流程、常见错误 |
|
||||
|
||||
旧的需求说明、技术实现、调试交接、Prompt 和单点问题复盘文档已经合并到上述文档中。临时调试结论不再作为根目录入口,避免把某一轮现场状态误认为长期项目事实。
|
||||
|
||||
## 一句话架构
|
||||
|
||||
```text
|
||||
远端 TCP 连接
|
||||
|
|
||||
v
|
||||
TcpSrvTask_S1/S2 或 TcpCliTask_C1/C2
|
||||
|
|
||||
v
|
||||
route_msg_t + FreeRTOS Queue
|
||||
|
|
||||
v
|
||||
UartRxTask / uart_trans
|
||||
|
|
||||
v
|
||||
USART2 / USART3
|
||||
```
|
||||
|
||||
反向数据流也走同一套路由对象:数据口收到字节后由 `UartRxTask` 判断普通透传或 MUX 帧,再投递到对应 `xLinkTxQueues[]`,最终由 TCP 任务调用 `netconn_write()` 发回网络。
|
||||
|
||||
## 快速阅读顺序
|
||||
|
||||
首次接手建议按下面顺序阅读:
|
||||
|
||||
1. `README.md`:确认硬件、软件、文档入口。
|
||||
2. `项目代码阅读指南.md` 的“总体架构”和“业务数据流示例”:先建立整体图。
|
||||
3. `Core/Src/main.c`:理解上电初始化顺序。
|
||||
4. `Core/Src/freertos.c`:理解队列、信号量和任务创建。
|
||||
5. `App/config.c`、`AT固件使用手册.md`:理解配置模型和 AT 命令。
|
||||
6. `App/tcp_server.c`、`App/tcp_client.c`、`App/uart_trans.c`、`App/route_msg.c`:理解 TCP 与 UART 如何互相转发。
|
||||
7. `App/task_net_poll.c`、`Drivers/CH390/*`、`Drivers/LwIP/src/netif/ethernetif.c`:需要定位网络底层问题时再深入。
|
||||
|
||||
## 核心配置模型
|
||||
|
||||
固件内部把所有链路抽象为 4 条 `LINK`:
|
||||
|
||||
| 角色 | 内部索引 | 端点编码 | 含义 |
|
||||
|------|----------|----------|------|
|
||||
| `S1` | `CONFIG_LINK_S1` | `0x10` | TCP Server 1 |
|
||||
| `S2` | `CONFIG_LINK_S2` | `0x20` | TCP Server 2 |
|
||||
| `C1` | `CONFIG_LINK_C1` | `0x01` | TCP Client 1 |
|
||||
| `C2` | `CONFIG_LINK_C2` | `0x02` | TCP Client 2 |
|
||||
| `UART2` / `U0` | `LINK_UART_U0` | `0x04` | 数据串口 0 |
|
||||
| `UART3` / `U1` | `LINK_UART_U1` | `0x08` | 数据串口 1 |
|
||||
|
||||
每条 `LINK` 记录包含:启用状态、本地端口、远端 IP、远端端口、绑定的数据串口。外部配置命令见 `AT固件使用手册.md`。
|
||||
|
||||
## 常用开发入口
|
||||
|
||||
| 目的 | 文件 |
|
||||
|------|------|
|
||||
| 上电初始化 | `Core/Src/main.c` |
|
||||
| 任务和队列创建 | `Core/Src/freertos.c` |
|
||||
| AT 命令与 Flash 参数 | `App/config.c`、`App/flash_param.c` |
|
||||
| 路由消息池 | `App/route_msg.c` |
|
||||
| UART DMA/IDLE、普通透传、MUX 帧 | `App/uart_trans.c` |
|
||||
| TCP Server | `App/tcp_server.c` |
|
||||
| TCP Client | `App/tcp_client.c` |
|
||||
| 网络初始化和轮询 | `App/task_net_poll.c` |
|
||||
| CH390 与 lwIP netif | `Drivers/CH390/*`、`Drivers/LwIP/src/netif/ethernetif.c` |
|
||||
| RTT 日志封装 | `Core/Src/debug_log.c` |
|
||||
|
||||
## 调试提示
|
||||
|
||||
1. 启动阶段先看 RTT 中的 `debug_log_boot()` 里程碑,例如 `hal-init`、`clock-config`、`peripherals-ready`、`tasks-created`、`scheduler-start`。
|
||||
2. 网络阶段重点看 `NetPollTask` 的 `tcpip-init`、`netif-init`、`netif-ready`、`post-ready free/min heap` 日志。
|
||||
3. TCP 或 UART 透传异常时,优先检查 `route_send()` 返回的 `pool`、`queue`、`invalid`,它们能区分消息池耗尽、队列满和参数错误。
|
||||
4. HardFault、MemManage、BusFault、UsageFault 会进入 `Debug_TrapWithRttHint()`,应先保留 RTT 现场再修改代码。
|
||||
5. `STM32F103RCT6` RAM 余量有限;如果 full-task 模式下问题呈现强资源相关特征,应优先核对堆、栈、水位和 lwIP 池配置,不要只看业务逻辑。
|
||||
|
||||
更详细的阅读和调试路线见 `项目代码阅读指南.md`。
|
||||
@@ -1,208 +0,0 @@
|
||||
# TCP2UART 调试交接清单
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本清单用于把当前 `TCP2UART` 工程的调试状态、已验证结论、后续动作建议一次性交接给下一位 agent 或开发者。
|
||||
|
||||
这份文档本身就可以当作下一位 agent 的工作 prompt 使用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 先读哪些文档
|
||||
|
||||
接手本工程后,推荐按以下顺序阅读:
|
||||
|
||||
1. `交接清单.md` —— 当前状态、下一步、禁止回退到哪些旧假设
|
||||
2. `工程调试指南.md` —— 已固化的调试经验与真实工程边界
|
||||
3. `项目技术实现.md` —— 架构、任务模型、协议模型
|
||||
4. `项目需求说明.md` —— 用户需求与协议要求
|
||||
5. `AT固件使用手册.md` —— AT 命令与配置面
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前工程状态(交接时刻)
|
||||
|
||||
### 3.1 当前平台与构建状态
|
||||
|
||||
1. 当前 Keil 工程目标仍是 `STM32F103RC`
|
||||
2. 当前代码可以真实构建通过
|
||||
3. 当前构建真值应查看:
|
||||
- `MDK-ARM/build_capture.txt`
|
||||
- `MDK-ARM/TCP2UART/TCP2UART.build_log.htm`
|
||||
- `MDK-ARM/TCP2UART/TCP2UART.map`
|
||||
4. 最近一次 Keil 构建示例:
|
||||
- `Code=84560`
|
||||
- `RW-data=432`
|
||||
- `ZI-data=47056`
|
||||
- `0 Error(s), 0 Warning(s)`
|
||||
|
||||
### 3.2 当前调试结论摘要
|
||||
|
||||
1. `DIAG_TASK_ISOLATION=1` 稳定
|
||||
2. `DIAG_TASK_ISOLATION=0` 仍会卡死
|
||||
3. 卡死边界已经从更早的启动阶段被推进到更靠后的 enabled `netconn_*` 路径
|
||||
4. 在 `RCT6` 上,四个 TCP task 创建后 `FreeRTOS heap` 仅剩约 `944 bytes`
|
||||
5. 这说明当前 `RCT6` 上的资源余量已经严重干扰调试判断
|
||||
6. 因此当前推荐策略是:**先换到 pin2pin 的 `STM32F103RDT6`,再继续 full-task 调试**
|
||||
|
||||
### 3.3 已做过并有信息量的改动/观察
|
||||
|
||||
以下工作已经做过,不要在没有新理由的情况下重复一遍:
|
||||
|
||||
1. 清理与恢复 `DIAG_TASK_ISOLATION=0`
|
||||
2. 用真实 Keil 日志替代 viewer 作为构建真值
|
||||
3. staged creation:将四个 TCP task 延后到 `netif-ready` 后创建
|
||||
4. `lwIP netif` 初始化、post-init、post-ready 关键 RTT 日志
|
||||
5. CH390 TX bounded wait / timeout 观察
|
||||
6. TCP task 栈从 `256` 提高到 `384 words`
|
||||
7. TCP task 入口 `hwm` 日志
|
||||
8. 对 `C1` 增加 one-shot first-connect defer discriminator
|
||||
|
||||
这些动作都让故障边界后移了,但仍未在 `RCT6` 上把问题彻底消灭。
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前最可信的判断
|
||||
|
||||
当前最可信的判断不是“某一行代码单点必错”,而是:
|
||||
|
||||
1. `RCT6` 上的静态 RAM 占用与 FreeRTOS heap 余量都已经接近极限
|
||||
2. disabled 的 task 可以持续打印,说明调度器与基础日志路径未整体死亡
|
||||
3. enabled 的 `S1 / C1` 一旦进入真实 `netconn_*` 路径,就更容易触发新的运行期问题
|
||||
4. 因此如果继续在 `RCT6` 上做更多 discriminator,很容易一直被资源边界噪声带偏
|
||||
|
||||
换句话说,**先把“内存极限平台”这个干扰项拿掉,再看逻辑问题还剩多少,是当前性价比最高的路线。**
|
||||
|
||||
---
|
||||
|
||||
## 5. 下一位 agent 现在应该做什么
|
||||
|
||||
### 5.1 第一目标
|
||||
|
||||
先完成 `STM32F103RCT6 -> STM32F103RDT6` 的工程切换,然后在**尽量不再改业务逻辑**的前提下复现当前版本。
|
||||
|
||||
### 5.2 为什么先这样做
|
||||
|
||||
因为下一位 agent 最先需要回答的,不是“立刻怎么修”,而是:
|
||||
|
||||
1. 换片后 full-task 模式是否还挂
|
||||
2. 如果还挂,挂点是否后移
|
||||
3. 换片后 `free/min heap` 是否显著改善
|
||||
4. enabled `S1 / C1` 是否能进入更深的 `netconn_*` 路径
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一位 agent 的推荐工作步骤
|
||||
|
||||
### Step 1:切换目标器件到 `STM32F103RDT6`
|
||||
|
||||
建议动作:
|
||||
|
||||
1. 更新 Keil target 里的 device 选择
|
||||
2. 对齐 Flash / RAM 容量描述
|
||||
3. 确认 linker / scatter / startup 相关目标描述与新器件一致
|
||||
4. 再次真实构建,确认 `0 Error(s), 0 Warning(s)`
|
||||
|
||||
### Step 2:保持当前代码基线,直接上板复测
|
||||
|
||||
要求:
|
||||
|
||||
1. 不要一换片就继续改业务逻辑
|
||||
2. 使用当前代码基线直接验证
|
||||
3. 仍以 `build_capture.txt` 和 RTT 为主证据
|
||||
|
||||
### Step 3:收集第一轮换片后的关键证据
|
||||
|
||||
至少记录:
|
||||
|
||||
1. `netif-ready` 后是否仍卡死
|
||||
2. `free/min heap` 变化
|
||||
3. enabled `S1 / C1` 的新 RTT 行为
|
||||
4. `C1 first-connect defer` 是否仍然影响故障边界
|
||||
5. LED 心跳和 IWDG 表现是否变化
|
||||
|
||||
### Step 4:根据换片结果分流
|
||||
|
||||
#### 情况 A:换片后明显稳定 / 不再挂
|
||||
|
||||
说明:
|
||||
|
||||
1. RAM 压力是主要阻碍项之一
|
||||
2. 后续需要回头做“资源预算收敛”,而不是继续把当前临时参数直接当最终方案
|
||||
|
||||
#### 情况 B:换片后仍挂,但更晚 / 更深
|
||||
|
||||
说明:
|
||||
|
||||
1. 当前逻辑问题仍在
|
||||
2. 但已经去掉了最主要的资源噪声
|
||||
3. 这时再围绕 `S1 / C1` 的真实 `netconn_new / bind / connect / listen / accept` 做最小日志/判别,会更有信息量
|
||||
|
||||
#### 情况 C:换片后几乎同点位仍挂
|
||||
|
||||
说明:
|
||||
|
||||
1. 主问题不再是单纯 RAM
|
||||
2. 应优先检查 enabled 路径的 API 使用、阻塞行为、线程间交互与资源释放
|
||||
|
||||
---
|
||||
|
||||
## 7. 不要重复的方向
|
||||
|
||||
下一位 agent 接手后,除非有新证据,否则不要优先回到以下旧方向:
|
||||
|
||||
1. 单纯怀疑 startup/init 早期 bring-up
|
||||
2. 把 viewer 当构建真值
|
||||
3. 继续只靠加大 TCP task 栈来解释所有现象
|
||||
4. 默认把 CH390 TX timeout 当成当前一号嫌疑
|
||||
5. 在 `RCT6` 上继续大量增加日志、队列、栈或临时 buffer
|
||||
|
||||
---
|
||||
|
||||
## 8. 关键文件
|
||||
|
||||
### 构建/目标
|
||||
|
||||
1. `MDK-ARM/TCP2UART.uvprojx`
|
||||
2. `MDK-ARM/build_capture.txt`
|
||||
3. `MDK-ARM/TCP2UART/TCP2UART.build_log.htm`
|
||||
4. `MDK-ARM/TCP2UART/TCP2UART.map`
|
||||
|
||||
### 任务与运行期
|
||||
|
||||
1. `Core/Inc/FreeRTOSConfig.h`
|
||||
2. `Core/Src/freertos.c`
|
||||
3. `App/task_net_poll.c`
|
||||
4. `App/tcp_server.c`
|
||||
5. `App/tcp_client.c`
|
||||
|
||||
### 网络与驱动
|
||||
|
||||
1. `Drivers/LwIP/src/netif/ethernetif.c`
|
||||
2. `Drivers/CH390/CH390.c`
|
||||
3. `Drivers/CH390/CH390.h`
|
||||
|
||||
### 文档
|
||||
|
||||
1. `工程调试指南.md`
|
||||
2. `项目技术实现.md`
|
||||
3. `交接清单.md`
|
||||
4. `CODING_PROMPT.md`
|
||||
|
||||
---
|
||||
|
||||
## 9. 可直接给下一位 agent 的 Prompt
|
||||
|
||||
下面这段文字可以直接作为下一位 agent 的起始 prompt:
|
||||
|
||||
```text
|
||||
请先阅读:`交接清单.md`、`工程调试指南.md`、`项目技术实现.md`。
|
||||
|
||||
当前项目是 STM32F103 + FreeRTOS + lwIP + CH390 的 TCP↔UART 透传工程。此前在 `STM32F103RCT6` 上调试时,`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前最关键的资源事实是:在 `RCT6` 上 full-task 创建完四个 TCP 任务后,FreeRTOS heap 只剩约 944 bytes,静态 RAM 也已逼近物理上限,因此当前推荐先切换到 pin2pin 的 `STM32F103RDT6`,保持现有代码基线基本不变,先完成第一轮换片复测,再根据新器件上的 RTT、free/min heap 和 enabled `S1/C1` 行为决定下一步。
|
||||
|
||||
你的当前目标不是立刻修完所有问题,而是:
|
||||
1. 完成 `RCT6 -> RDT6` 目标切换;
|
||||
2. 用真实 Keil 日志确认构建通过;
|
||||
3. 在新器件上复测当前代码,判断故障是否消失、后移或保持原状;
|
||||
4. 仅在拿到新器件上的第一轮 RTT 后,再继续做最小化的下一步判别。
|
||||
```
|
||||
@@ -1,450 +0,0 @@
|
||||
# TCP2UART 调试指导
|
||||
|
||||
## 1. 适用范围
|
||||
|
||||
本指导面向当前 `TCP2UART` 工程,覆盖以下四类调试场景:
|
||||
|
||||
1. `STM32F103RCT6 + CH390D` 的基础 bring-up
|
||||
2. `SEGGER RTT`、异常陷阱与 FreeRTOS 任务运行状态确认
|
||||
3. `USART1` 配置口、`USART2/USART3` 数据口与 `MUX / NET / LINK[idx]` 协议联调
|
||||
4. `TCP Server / TCP Client / UART` 三层数据通路联调与问题隔离
|
||||
|
||||
本指导默认基线如下:
|
||||
|
||||
1. 当前工程采用 `FreeRTOS` 任务调度架构
|
||||
2. `CH390` 运行时访问由 `xSpiMutex` 保护,`NetworkTask` 持有主要访问权
|
||||
3. 调试输出统一使用 `SEGGER RTT`
|
||||
4. 当前应用层协议模型已经收敛到 `MUX / NET / LINK[idx]`
|
||||
5. 当前代码应以 `MDK-ARM` 工程构建结果为准
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前工程边界与真实状态
|
||||
|
||||
在进入现场调试前,先统一以下工程边界:
|
||||
|
||||
1. 当前项目的主要软件路径已经切换为:
|
||||
- `NET`:网络基础参数
|
||||
- `LINK[idx]`:链路配置记录
|
||||
- `MUX`:数据口承载模式
|
||||
2. 对外 AT 配置面应只围绕以下命令展开:
|
||||
- `AT` / `AT+?` / `AT+QUERY`
|
||||
- `AT+MUX` / `AT+NET` / `AT+LINK`
|
||||
- `AT+SAVE` / `AT+RESET` / `AT+DEFAULT`
|
||||
3. 已有结论表明:
|
||||
- MCU 启动、RTT、FreeRTOS 调度、TIM4 心跳路径可工作
|
||||
- `CH390D` 基础寄存器读写与 `lwIP netif` 基本链路已经打通过一次
|
||||
- 真实硬件侧曾定位到 `CH390D` 供电滤波电容虚焊问题
|
||||
4. 当前调试重点是:
|
||||
- FreeRTOS 任务是否正常创建与调度
|
||||
- `MUX / NET / LINK[idx]` 协议是否与代码一致
|
||||
- UART / TCP / CH390 三层通路是否协同稳定
|
||||
- 参数保存、复位和恢复流程是否可靠
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码入口与调试责任边界
|
||||
|
||||
### 3.1 启动与 FreeRTOS 入口
|
||||
|
||||
以下代码路径是 bring-up 的第一现场:
|
||||
|
||||
1. `Core/Src/main.c`
|
||||
- `main()`:总启动入口
|
||||
- `SystemClock_Config()`:时钟初始化
|
||||
- `MX_FREERTOS_Init()`:FreeRTOS 任务创建(在 `freertos.c` 中实现)
|
||||
2. `Core/Src/freertos.c`
|
||||
- `StartDefaultTask()`:默认任务(LED 心跳 + 看门狗)
|
||||
- `MX_FREERTOS_Init()`:用户任务创建入口
|
||||
3. `Core/Src/stm32f1xx_it.c`
|
||||
- 故障与中断入口
|
||||
- `TIM4_IRQHandler`:HAL 时间基准
|
||||
- `USART1/2/3`、`EXTI0`、DMA 回调等联调关键入口
|
||||
|
||||
### 3.2 CH390 责任边界
|
||||
|
||||
当前 CH390 调试必须遵守以下责任边界:
|
||||
|
||||
1. `Drivers/CH390/CH390_Interface.c`:GPIO / SPI / 寄存器与内存事务
|
||||
2. `Drivers/CH390/CH390.c`:芯片级 helper
|
||||
3. `Drivers/CH390/ch390_runtime.c`:唯一的运行时拥有者
|
||||
4. `Drivers/LwIP/src/netif/ethernetif.c`:netif glue 与轮询桥接
|
||||
5. SPI 访问由 `xSpiMutex` 保护,避免多任务竞争
|
||||
|
||||
### 3.3 配置口与业务口边界
|
||||
|
||||
1. `USART1`:AT 配置口,接收 `AT` 命令
|
||||
2. `USART2 / USART3`:数据口,普通透传或 MUX 承载
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前硬件与调试工具基线
|
||||
|
||||
### 4.1 核心硬件对象
|
||||
|
||||
1. MCU:`STM32F103RCT6`(256KB Flash / 48KB SRAM)
|
||||
2. 以太网芯片:`CH390D`
|
||||
3. 配置串口:`USART1`
|
||||
4. 数据串口:`USART2 / USART3`
|
||||
5. 调试输出:`SEGGER RTT`
|
||||
|
||||
### 4.2 构建与下载基线
|
||||
|
||||
1. `MDK-ARM/TCP2UART.uvprojx`
|
||||
2. 启动文件:`startup_stm32f103xe.s`
|
||||
3. 目标器件:`STM32F103RC`
|
||||
4. 预处理器宏:`USE_HAL_DRIVER, STM32F103xE`
|
||||
|
||||
### 4.3 常用调试工具
|
||||
|
||||
1. `Keil MDK-ARM`
|
||||
2. `ST-Link / J-Link`
|
||||
3. `SEGGER RTT Viewer`
|
||||
4. `PowerShell`
|
||||
5. `tools/start_tcp_debug_server.ps1`
|
||||
6. `tools/tcp_debug_server.py`
|
||||
|
||||
---
|
||||
|
||||
## 5. FreeRTOS 专项调试
|
||||
|
||||
### 5.1 任务状态检查
|
||||
|
||||
使用 `vTaskList` 获取所有任务运行状态:
|
||||
|
||||
```c
|
||||
char buf[512];
|
||||
vTaskList(buf);
|
||||
SEGGER_RTT_WriteString(0, buf);
|
||||
```
|
||||
|
||||
输出格式:
|
||||
|
||||
```text
|
||||
任务名 状态 优先级 剩余栈 编号
|
||||
NetworkTask R 4 120 1
|
||||
UartTask B 4 200 2
|
||||
ConfigTask B 3 150 3
|
||||
RouteTask R 3 180 4
|
||||
DefaultTask B 1 80 5
|
||||
IDLE R 0 100 6
|
||||
Tmr Svc B 2 90 7
|
||||
```
|
||||
|
||||
状态码:`R`=Ready, `B`=Blocked, `S`=Suspended, `D`=Deleted, `I`=Invalid
|
||||
|
||||
### 5.2 栈溢出检测
|
||||
|
||||
已启用 `configCHECK_FOR_STACK_OVERFLOW = 2`,溢出时自动调用:
|
||||
|
||||
```c
|
||||
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
|
||||
{
|
||||
SEGGER_RTT_printf(0, "STACK OVERFLOW: %s\n", pcTaskName);
|
||||
__BKPT(0);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 堆内存失败检测
|
||||
|
||||
已启用 `configUSE_MALLOC_FAILED_HOOK`,分配失败时自动调用:
|
||||
|
||||
```c
|
||||
void vApplicationMallocFailedHook(void)
|
||||
{
|
||||
SEGGER_RTT_printf(0, "MALLOC FAILED: Free heap = %u\n", xPortGetFreeHeapSize());
|
||||
__BKPT(0);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 常见 FreeRTOS 调试陷阱
|
||||
|
||||
1. **优先级反转**:使用 Mutex(含优先级继承)而非 Binary Semaphore 保护共享资源
|
||||
2. **死锁**:多 Mutex 场景确保所有任务按相同顺序获取
|
||||
3. **中断优先级**:FreeRTOS 可管理的 ISR 优先级必须 >= `configMAX_SYSCALL_INTERRUPT_PRIORITY`(本工程 5)
|
||||
4. **栈不足**:每个任务定期调用 `uxTaskGetStackHighWaterMark(NULL)` 检查剩余栈
|
||||
5. **禁止在中断中调用阻塞 API**:必须使用 `FromISR` 后缀版本
|
||||
|
||||
---
|
||||
|
||||
## 6. 启动阶段调试顺序
|
||||
|
||||
建议按 P0 ~ P5 顺序推进,不要跳层。
|
||||
|
||||
### 6.1 P0:确认最小基础条件
|
||||
|
||||
1. `MDK-ARM` 可构建并产出新的 `axf/hex/map`
|
||||
2. 板卡可正常下载与复位
|
||||
3. RTT 可连接并看到启动输出
|
||||
4. FreeRTOS 任务创建成功,`DefaultTask` LED 心跳可工作
|
||||
5. `TIM4` 1ms tick 正常运行
|
||||
|
||||
### 6.2 P1:确认 FreeRTOS 调度正常
|
||||
|
||||
上电或复位后,优先确认:
|
||||
|
||||
1. `StartDefaultTask` 是否进入运行
|
||||
2. `vTaskList` 输出是否显示所有预期任务
|
||||
3. `xPortGetFreeHeapSize()` 返回值是否合理
|
||||
4. 无 `STACK OVERFLOW` 或 `MALLOC FAILED` 输出
|
||||
|
||||
### 6.3 P2:确认 CH390 初始化链路
|
||||
|
||||
启动阶段应重点关注 `NetworkTask` 中初始化日志:
|
||||
|
||||
1. `ETH init: gpio`
|
||||
2. `ETH init: spi`
|
||||
3. `ETH init: reset`
|
||||
4. `ETH init: probe`
|
||||
5. `ETH init: default`
|
||||
6. `ETH init: mac`
|
||||
7. `ETH init: done`
|
||||
|
||||
### 6.4 P3:确认 TCP 链路
|
||||
|
||||
1. lwIP `tcpip_thread` 是否正常运行
|
||||
2. TCP Server 是否在指定端口监听
|
||||
3. TCP Client 是否成功连接远端
|
||||
|
||||
### 6.5 P3.5:确认 ARP / ICMP 基础网络可达
|
||||
|
||||
在继续 TCP 联调前,建议先把 `ARP + ping(ICMP)` 跑通。对于当前 `CH390 + lwIP + FreeRTOS` 架构,这一步不是可选项,而是 TCP 可达之前必须成立的网络基线。
|
||||
|
||||
#### 6.5.1 推荐最小验证顺序
|
||||
|
||||
1. 先确认板卡 IP、掩码、MAC 与 PC 所在网段一致
|
||||
2. 上电后先观察 RTT,确认 `ETH init: done` 已出现
|
||||
3. 在 PC 侧执行一次 `ping <板卡IP>`,同时开启 Wireshark 抓包
|
||||
4. 先看是否出现发往板卡 IP 的 ARP request,再看设备是否回 ARP reply
|
||||
5. ARP 正常后,再看是否出现 ICMP echo request / echo reply 成对出现
|
||||
|
||||
#### 6.5.2 当前工程推荐观察点
|
||||
|
||||
如果网络基础链路有疑问,建议按以下分层观察,不要只看某一层:
|
||||
|
||||
1. **raw RX 层**:CH390 是否确实收到了以太网帧
|
||||
2. **Ethernet demux 层**:`ethernet_input()` 是否识别到 `ETHTYPE_ARP` / `ETHTYPE_IP`
|
||||
3. **协议处理层**:`etharp_input()` / `ip_input()` 是否真正进入
|
||||
4. **协议发包层**:lwIP 是否已经生成待发送的 ARP reply / ICMP reply
|
||||
5. **驱动发送层**:`low_level_output()` / CH390 TX 是否真正把帧送出
|
||||
|
||||
这次 bring-up 证明,`raw RX 正常` 并不等于 `lwIP 已真正处理该帧`。如果只看到底层收到了包,就直接假设协议栈一定会回复,通常会把排查方向带偏。
|
||||
|
||||
#### 6.5.3 这次 ARP / ICMP bring-up 的关键结论
|
||||
|
||||
本轮调试中,最终根因位于:
|
||||
|
||||
- `Drivers/LwIP/src/netif/ethernet.c`
|
||||
|
||||
问题本质是:
|
||||
|
||||
1. `ethernet_input()` 在 `ETHTYPE_ARP` 分支中,曾直接调用 `etharp_input(p, netif)`
|
||||
2. 但 `etharp_input()` 要求 `p->payload` 从 **ARP 头** 开始,而不是从 Ethernet 头开始
|
||||
3. 因此如果没有先执行:
|
||||
|
||||
```c
|
||||
pbuf_remove_header(p, SIZEOF_ETH_HDR)
|
||||
```
|
||||
|
||||
则 ARP 包虽然被收到了,但会在 `etharp_input()` 的早期校验中被静默丢弃,最终表现为:
|
||||
|
||||
1. Wireshark 能看到 PC 发来的 ARP request
|
||||
2. 板子侧底层收包计数在增长
|
||||
3. 但设备始终不回 ARP reply
|
||||
4. ping 也自然不会成功
|
||||
|
||||
当前应保留的正确处理方式如下:
|
||||
|
||||
```c
|
||||
case ETHTYPE_ARP:
|
||||
if (netif->flags & NETIF_FLAG_ETHARP) {
|
||||
if (pbuf_remove_header(p, SIZEOF_ETH_HDR)) {
|
||||
pbuf_free(p);
|
||||
return ERR_OK;
|
||||
}
|
||||
etharp_input(p, netif);
|
||||
} else {
|
||||
pbuf_free(p);
|
||||
}
|
||||
return ERR_OK;
|
||||
```
|
||||
|
||||
#### 6.5.4 为什么这类问题容易漏看
|
||||
|
||||
这类问题常见但隐蔽,原因通常有三点:
|
||||
|
||||
1. 根因非常小,外在表现却像“整个发送链路都坏了”
|
||||
2. 多个低层信号可能同时正常,容易误导为 SPI / TX / CH390 初始化问题
|
||||
3. `rx ok`、`ARP 帧计数在涨`、`链路已 up` 都不代表协议层一定接受了该帧
|
||||
|
||||
因此,后续遇到“收得到包但就是不回”的问题时,优先检查:
|
||||
|
||||
1. 传给上层协议处理函数时,`pbuf->payload` 是否已经对齐到正确协议头
|
||||
2. glue-layer 是否和 lwIP 原生调用约定一致
|
||||
3. 观察点是否已经覆盖到 `demux -> protocol handler -> linkoutput` 这一整条链
|
||||
|
||||
#### 6.5.5 建议保留的最小验收标准
|
||||
|
||||
在认定网络基线“已经打通”之前,至少应满足:
|
||||
|
||||
1. Keil 工程可稳定构建通过
|
||||
2. 上电后可稳定看到网络初始化完成日志
|
||||
3. Wireshark 中能看到设备对本机 ARP request 做出 reply
|
||||
4. PC 对设备 `ping` 时,能看到 ICMP echo request / reply 成对出现
|
||||
5. RTT 中无 `STACK OVERFLOW`、`MALLOC FAILED`、异常 trap 等故障信号
|
||||
|
||||
---
|
||||
|
||||
## 7. MUX / NET / LINK[idx] 联调指导
|
||||
|
||||
### 7.1 协议总则
|
||||
|
||||
与裸机版本完全一致,参见 `AT固件使用手册.md`。
|
||||
|
||||
### 7.2 推荐最小 MUX 联调顺序
|
||||
|
||||
1. 先在 `MUX=0` 下跑通原始透传
|
||||
2. 再切换 `MUX=1`
|
||||
3. 先发一个控制帧,确认 `DSTMASK=0x00` 路径可通
|
||||
4. 再发一个单目标数据帧
|
||||
5. 最后验证多目标位图转发
|
||||
|
||||
---
|
||||
|
||||
## 8. 异常、卡死与假死排查
|
||||
|
||||
### 8.1 看到 `TRAP:` 时怎么做
|
||||
|
||||
1. 先记录 RTT 中的 trap 标签
|
||||
2. 立刻用调试器查看当前 PC / LR / 调用栈
|
||||
3. 结合 `Core/Src/stm32f1xx_it.c` 中对应 handler 定位异常类型
|
||||
|
||||
### 8.2 FreeRTOS 任务卡死时怎么做
|
||||
|
||||
1. 使用 `vTaskList` 检查各任务状态
|
||||
2. 如果某个任务始终 `B`(Blocked),检查其等待的队列/信号量
|
||||
3. 检查是否有 Mutex 被持有但从未释放
|
||||
4. 使用调试器暂停,查看各任务的调用栈
|
||||
|
||||
### 8.3 常见 FreeRTOS 陷阱
|
||||
|
||||
1. 在 ISR 中误调用阻塞 API(如 `xQueueSend` 而非 `xQueueSendFromISR`)
|
||||
2. 中断优先级低于 `configMAX_SYSCALL_INTERRUPT_PRIORITY` 但调用了 FreeRTOS API
|
||||
3. Mutex 持有期间任务被删除导致 Mutex 永不释放
|
||||
4. 栈溢出导致邻近变量被破坏
|
||||
|
||||
---
|
||||
|
||||
## 9. 常见误区
|
||||
|
||||
1. 不要继续沿用"CH390 恒为全 `0xFF`"过时结论
|
||||
2. 不要在多个任务中直接访问 CH390 SPI(必须通过 Mutex 保护)
|
||||
3. 不要在没有芯片脚侧证据前,只凭 GPIO 判断总线正常
|
||||
4. 不要在基础寄存器读写尚不可信时,直接调高层业务
|
||||
5. 不要在 ISR 中执行复杂 SPI 事务或调用阻塞 API
|
||||
6. 不要忽视 `configCHECK_FOR_STACK_OVERFLOW` 报告
|
||||
7. 不要把“底层已经收到 ARP 包”等同于“lwIP 一定已经正确处理 ARP 包”
|
||||
8. 不要忽略 glue-layer 对 `pbuf->payload` 起始位置的约定,特别是 `Ethernet header -> ARP/IP header` 的切换
|
||||
9. 不要在 ARP / ICMP 还没闭环前,就直接怀疑 TCP Server / TCP Client 逻辑
|
||||
10. 不要在没有抓包和分层观测点的情况下,只凭单一日志就断言故障位于 TX 或 SPI 层
|
||||
|
||||
---
|
||||
|
||||
## 10. 推荐配套阅读
|
||||
|
||||
1. `AT固件使用手册.md`
|
||||
2. `项目技术实现.md`
|
||||
3. `项目需求说明.md`
|
||||
4. `Keil工程配置说明.txt`
|
||||
|
||||
---
|
||||
|
||||
## 11. 近期调试经验固化(2026-04)
|
||||
|
||||
本节用于固化这一轮 `FreeRTOS + lwIP + CH390` 联调过程中已经验证过的经验,后续调试默认以本节为前提,不要反复回到已排除的旧假设。
|
||||
|
||||
### 11.1 构建结果的真值来源
|
||||
|
||||
当前工程的构建真值必须以 `Keil` 实际构建日志为准,而不是辅助 viewer。
|
||||
|
||||
推荐优先级如下:
|
||||
|
||||
1. `MDK-ARM/build_capture.txt`
|
||||
2. `MDK-ARM/TCP2UART/TCP2UART.build_log.htm`
|
||||
3. `MDK-ARM/TCP2UART/TCP2UART.map`
|
||||
|
||||
说明:
|
||||
|
||||
1. `keil-build-viewer.exe` 只能辅助观察内存占用或旧构建快照
|
||||
2. viewer 未刷新时,不代表当前代码没有变化,往往只是最新构建没成功或 viewer 数据滞后
|
||||
3. 后续任何“编译通过 / RAM 变化 / 目标器件切换”的判断,都必须引用以上三类 Keil 真实产物
|
||||
|
||||
### 11.2 近期已验证的事实
|
||||
|
||||
以下结论已经被本轮调试反复验证:
|
||||
|
||||
1. `DIAG_TASK_ISOLATION=1` 时,系统可以稳定运行
|
||||
2. `DIAG_TASK_ISOLATION=0` 时,full-task 模式仍会卡死
|
||||
3. 启动阶段 `lwip_netif_init()`、deferred `xTaskCreate()`、`netif-ready` 等关键日志已经真实跑通
|
||||
4. 之前通过加日志、分阶段创建 TCP 任务、调整栈大小后,卡死边界会继续后移,但问题不会完全消失
|
||||
5. 这说明当前问题不是单一固定点故障,而是“逻辑路径 + 极低资源余量”共同作用的结果
|
||||
|
||||
### 11.3 已排除或降级优先级的方向
|
||||
|
||||
以下方向目前不再应作为一号假设:
|
||||
|
||||
1. **startup/init 失败**
|
||||
- 当前日志已能稳定走到 `netif-ready`
|
||||
- 因此主要问题不再是最初的 bring-up 链路本身
|
||||
2. **单纯 CH390 TX 无限等待是当前主因**
|
||||
- 已为 TX 路径增加过 bounded wait / timeout 观察点
|
||||
- 最新故障没有先落到 `[ETH] tx timeout` 分支
|
||||
3. **只靠继续增大 TCP task 栈就能解决问题**
|
||||
- `256 -> 384 words` 后,故障边界虽然继续后移,但系统仍会卡死
|
||||
- 栈确实曾是问题的一部分,但不是唯一剩余问题
|
||||
|
||||
### 11.4 当前最重要的资源压力结论
|
||||
|
||||
当前 `STM32F103RCT6`(48KB SRAM)上的真实资源压力已经高到会直接干扰调试判断:
|
||||
|
||||
1. 真实构建中,`ZI-data` 已接近物理 RAM 上限(约 95%+)
|
||||
2. 在 `DIAG_TASK_ISOLATION=0` 下,创建完四个 TCP 任务后,`xPortGetFreeHeapSize()` 只剩约 `944 bytes`
|
||||
3. 这个余量不足以让后续 `netconn_*`、semaphore、mailbox、任务栈回旋空间保持清晰边界
|
||||
4. 因此当前在 `RCT6` 上继续做 discriminator 时,结果会持续混入“资源边界噪声”
|
||||
|
||||
这也是为什么本轮调试后半段已经把重点从“继续在 RCT6 上做更多小修补”切换到“先换更大 RAM 的 pin2pin 器件再继续分析”。
|
||||
|
||||
### 11.5 当前推荐的硬件调试策略
|
||||
|
||||
当前推荐的下一阶段策略如下:
|
||||
|
||||
1. 先从 `STM32F103RCT6` 切换到 pin2pin 的 `STM32F103RDT6`
|
||||
2. 在切换器件后,尽量保持当前代码基线不做大改
|
||||
3. 先复测当前版本的 RTT 与运行边界,看故障是否:
|
||||
- 消失
|
||||
- 明显后移
|
||||
- 仍然停在相同 enabled path
|
||||
4. 再根据新器件上的表现,区分:
|
||||
- 资源压力主导
|
||||
- 逻辑 / 时序 / API 使用问题仍然存在
|
||||
|
||||
### 11.6 换片后第一轮调试目标
|
||||
|
||||
切换到 `RDT6` 后,第一轮调试不追求立刻修复,而是优先回答下面几个问题:
|
||||
|
||||
1. 当前 full-task 模式是否仍然卡死
|
||||
2. `free/min heap` 是否明显高于 `RCT6` 版本
|
||||
3. enabled 的 `S1 / C1` 是否能够继续进入更深的 `netconn_new / bind / connect / listen / accept` 路径
|
||||
4. 之前为了定位问题而加入的 discriminator(例如 `C1 first-connect defer`)是否仍然影响故障边界
|
||||
|
||||
如果这些问题不先回答,后续继续改代码的结论可信度会很差。
|
||||
|
||||
### 11.7 当前建议保留的调试原则
|
||||
|
||||
后续 agent 或开发者继续接手本工程时,建议遵守以下原则:
|
||||
|
||||
1. 不要在 `RCT6` 上继续大量加日志、加栈、加 queue 深度后再试图解释现象
|
||||
2. 不要把 viewer 当作当前构建真值
|
||||
3. 不要忽略 `DIAG_TASK_ISOLATION=1 正常、=0 异常` 这个前提
|
||||
4. 不要一次性修改 `C1/S1/CH390/lwIP` 多个方向,避免再次失去因果关系
|
||||
5. 每次只做一个能明显改变故障边界的最小改动,并用 RTT + Keil build 结果交叉验证
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
# TCP2UART 项目代码阅读指南
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本文面向接手 `TCP2UART` 的固件开发、联调和调试人员,目标是说明当前代码如何组织、启动后如何运行、TCP 与 UART 数据如何流动,以及应该按什么顺序阅读源码。
|
||||
|
||||
本文只描述当前工程的长期有效结构。历史调试交接文档中的某些“下一步动作”属于当时现场状态,不再作为当前项目入口。
|
||||
|
||||
## 2. 系统边界
|
||||
|
||||
### 2.1 硬件边界
|
||||
|
||||
- MCU:`STM32F103RCT6`
|
||||
- 以太网芯片:`CH390D`
|
||||
- 配置口:`USART1`
|
||||
- 数据口:`USART2`、`USART3`
|
||||
- 调试输出:`SEGGER RTT`
|
||||
|
||||
### 2.2 软件边界
|
||||
|
||||
- 工程:`MDK-ARM/TCP2UART.uvprojx`
|
||||
- 调度:`FreeRTOS`
|
||||
- 网络:`lwIP NO_SYS=0 + netconn API`
|
||||
- 网络输入:`tcpip_thread` + `ethernetif` + CH390 驱动
|
||||
- 业务链路:2 路 TCP Server,2 路 TCP Client,2 路 UART 数据口
|
||||
- 配置协议:`AT`、`MUX`、`NET`、`LINK`、`BAUD`、`SAVE`、`RESET`、`DEFAULT`
|
||||
|
||||
### 2.3 目录结构
|
||||
|
||||
```text
|
||||
App/
|
||||
app_runtime.h 全局任务、队列、信号量声明
|
||||
config.c/.h AT 命令、运行配置、默认值、Flash 保存
|
||||
flash_param.c/.h Flash 参数读写与 CRC
|
||||
route_msg.c/.h 固定消息池和路由消息封装
|
||||
task_net_poll.c/.h lwIP/CH390 初始化、netif ready、网络重启
|
||||
tcp_server.c/.h S1/S2 TCP Server 任务
|
||||
tcp_client.c/.h C1/C2 TCP Client 任务
|
||||
uart_trans.c/.h USART2/3 业务数据接收、发送、MUX 编解码
|
||||
|
||||
Core/Inc, Core/Src/
|
||||
main.c 上电入口和外设初始化顺序
|
||||
freertos.c FreeRTOS 队列、信号量、任务创建
|
||||
stm32f1xx_it.c 中断入口,尤其是 UART IDLE 和 CH390 EXTI
|
||||
usart.c/dma.c/... STM32CubeMX 生成的外设初始化
|
||||
debug_log.c/.h RTT 日志和异常提示
|
||||
|
||||
Drivers/CH390/
|
||||
CH390.c/.h 芯片级寄存器/辅助操作
|
||||
CH390_Interface.c SPI/GPIO 与 CH390 事务封装
|
||||
|
||||
Drivers/LwIP/
|
||||
src/include/arch/lwipopts.h 当前 lwIP 配置
|
||||
src/netif/ethernetif.c CH390 与 lwIP netif 胶水层
|
||||
port/sys_arch.c lwIP 在 FreeRTOS 上的 sys_arch 适配
|
||||
```
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
```text
|
||||
+------------------------------------------------------+
|
||||
| AT / Control Plane |
|
||||
| USART1 IDLE DMA -> ConfigTask -> config_process_at_cmd|
|
||||
+------------------------------------------------------+
|
||||
| Configuration Model |
|
||||
| MUX / NET / LINK[S1,S2,C1,C2] / BAUD |
|
||||
+------------------------------------------------------+
|
||||
| Routing Layer |
|
||||
| route_msg fixed pool + xTcpRxQueue + xLinkTxQueues[] |
|
||||
+------------------------------------------------------+
|
||||
| Data Tasks |
|
||||
| UartRxTask + TcpSrvTask_S1/S2 + TcpCliTask_C1/C2 |
|
||||
+------------------------------------------------------+
|
||||
| Network Runtime |
|
||||
| NetPollTask + tcpip_thread + ethernetif + CH390 |
|
||||
+------------------------------------------------------+
|
||||
| HAL / DMA / IRQ |
|
||||
| USART1/2/3 DMA+IDLE, SPI1, EXTI0, TIM4 timebase |
|
||||
+------------------------------------------------------+
|
||||
```
|
||||
|
||||
这套架构的核心思想是:TCP 任务和 UART 任务不直接互相调用,而是通过 `route_msg_t` 和 FreeRTOS 队列传递数据。这样可以把“从哪里来”“发到哪里去”“数据多长”统一表示,便于普通透传和 MUX 模式共用同一套路由机制。
|
||||
|
||||
## 4. 启动流程
|
||||
|
||||
启动入口在 `Core/Src/main.c` 的 `main()`。
|
||||
|
||||
1. `HAL_Init()` 初始化 HAL、Flash 接口和基础 tick。
|
||||
2. `debug_log_init()` 初始化 RTT 日志,并输出 `hal-init`。
|
||||
3. `SystemClock_Config()` 配置系统时钟。
|
||||
4. 初始化外设:`MX_GPIO_Init()`、`MX_DMA_Init()`、`MX_USART1_UART_Init()`。
|
||||
5. `config_init()` 从 Flash 读取配置;读取失败则 `config_set_defaults()`。
|
||||
6. `ApplyConfiguredUartBaudrates()` 根据配置设置 `USART2/USART3` 波特率。
|
||||
7. 初始化 `USART2`、`USART3`、`SPI1`。
|
||||
8. 初始化 LED 并执行 `CH390_HardwareReset()`。
|
||||
9. `osKernelInitialize()` 初始化 RTOS 内核。
|
||||
10. `MX_FREERTOS_Init()` 创建队列、信号量和基础任务。
|
||||
11. `osKernelStart()` 启动调度器。
|
||||
|
||||
调试启动问题时,优先按 RTT boot 日志确认流程卡在哪个里程碑。
|
||||
|
||||
## 5. FreeRTOS 对象和任务
|
||||
|
||||
`Core/Src/freertos.c` 是理解运行时结构的关键文件。
|
||||
|
||||
### 5.1 全局对象
|
||||
|
||||
| 对象 | 类型 | 用途 |
|
||||
|------|------|------|
|
||||
| `xNetSemaphore` | Binary Semaphore | `EXTI0` 通知网络轮询任务处理 CH390 事件 |
|
||||
| `xTcpRxQueue` | Queue | TCP 任务收到网络数据后投递给 `UartRxTask` |
|
||||
| `xConfigQueue` | Queue | `USART1` AT 命令或 MUX 控制帧投递给 `ConfigTask` |
|
||||
| `xLinkTxQueues[4]` | Queue | UART 收到的数据投递给指定 S1/S2/C1/C2 TCP 任务 |
|
||||
| `route_msg` pool | 固定池 | 避免每包动态分配,最大载荷见 `ROUTE_MSG_MAX_PAYLOAD` |
|
||||
|
||||
### 5.2 基础任务
|
||||
|
||||
`MX_FREERTOS_Init()` 固定创建:
|
||||
|
||||
| 任务 | 入口 | 职责 |
|
||||
|------|------|------|
|
||||
| `defaultTask` | `StartDefaultTask()` | LED 心跳、看门狗 |
|
||||
| `NetPoll` | `NetPollTask()` | 初始化 lwIP/netif,轮询或响应 CH390 中断,网络 ready 后启动 TCP 任务 |
|
||||
| `UartRx` | `UartRxTask()` | 处理 USART2/3 RX,执行普通透传或 MUX 路由 |
|
||||
| `Config` | `ConfigTask()` | 处理 AT 命令并回复 |
|
||||
|
||||
当 `DIAG_TASK_ISOLATION` 打开时,`UartRx` 和 `Config` 会被隔离,这只用于调试,不代表正常产品形态。
|
||||
|
||||
### 5.3 网络任务
|
||||
|
||||
网络任务不是在 `MX_FREERTOS_Init()` 里立即全部创建,而是在 `NetPollTask()` 完成 `tcpip_init()`、`lwip_netif_init()` 并设置 `g_netif_ready = pdTRUE` 后,由 `app_start_network_tasks()` 按配置创建:
|
||||
|
||||
| 任务 | 条件 | 职责 |
|
||||
|------|------|------|
|
||||
| `TcpSrvS1` | `LINK[S1].enabled` | 监听 S1 本地端口,收发 Server 数据 |
|
||||
| `TcpSrvS2` | `LINK[S2].enabled` | 监听 S2 本地端口,收发 Server 数据 |
|
||||
| `TcpCliC1` | `LINK[C1].enabled` | 主动连接 C1 远端,断线重连 |
|
||||
| `TcpCliC2` | `LINK[C2].enabled` | 主动连接 C2 远端,断线重连 |
|
||||
|
||||
这种延迟创建能避免 TCP 任务在 netif 尚未就绪时进入 `netconn_*` 路径。
|
||||
|
||||
## 6. 配置模型
|
||||
|
||||
配置结构定义在 `App/config.h` 的 `device_config_t`。
|
||||
|
||||
### 6.1 端点编码
|
||||
|
||||
| 端点 | 代码宏 | 编码 |
|
||||
|------|--------|------|
|
||||
| `C1` | `ENDPOINT_C1` | `0x01` |
|
||||
| `C2` | `ENDPOINT_C2` | `0x02` |
|
||||
| `UART2` / `U0` | `ENDPOINT_UART2` | `0x04` |
|
||||
| `UART3` / `U1` | `ENDPOINT_UART3` | `0x08` |
|
||||
| `S1` | `ENDPOINT_S1` | `0x10` |
|
||||
| `S2` | `ENDPOINT_S2` | `0x20` |
|
||||
|
||||
`SRCID` 是单一源端点;`DSTMASK` 是目标端点位图。`DSTMASK=0x00` 专用于系统控制帧,最终进入 AT 解析路径。
|
||||
|
||||
### 6.2 LINK 模型
|
||||
|
||||
4 条链路固定为:
|
||||
|
||||
| 角色 | 内部索引 | 默认作用 |
|
||||
|------|----------|----------|
|
||||
| `S1` | `CONFIG_LINK_S1` | TCP Server 1 |
|
||||
| `S2` | `CONFIG_LINK_S2` | TCP Server 2 |
|
||||
| `C1` | `CONFIG_LINK_C1` | TCP Client 1 |
|
||||
| `C2` | `CONFIG_LINK_C2` | TCP Client 2 |
|
||||
|
||||
每条 `LINK` 包含:
|
||||
|
||||
```text
|
||||
EN,LPORT,RIP,RPORT,UART
|
||||
```
|
||||
|
||||
`UART` 取 `U0` 或 `U1`,分别对应 `USART2` 和 `USART3`。
|
||||
|
||||
### 6.3 默认配置
|
||||
|
||||
当前默认值由 `config_set_defaults()` 写入:
|
||||
|
||||
- `MUX=0`,即普通透传模式。
|
||||
- `NET=192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00`。
|
||||
- `UART2/USART2` 和 `UART3/USART3` 默认波特率为 `115200`。
|
||||
- `reconnect_interval_ms=3000`。
|
||||
|
||||
完整 AT 命令格式以 `AT固件使用手册.md` 为准。
|
||||
|
||||
## 7. AT 配置数据流
|
||||
|
||||
AT 配置口固定使用 `USART1`。
|
||||
|
||||
```text
|
||||
上位机 AT 文本
|
||||
-> USART1 DMA 接收
|
||||
-> USART1 IDLE 中断
|
||||
-> config_uart_idle_handler()
|
||||
-> route_send_from_isr(xConfigQueue, ROUTE_CONN_UART1, ...)
|
||||
-> ConfigTask
|
||||
-> config_process_at_cmd()
|
||||
-> config_respond_to_uart()
|
||||
-> USART1 DMA 发送响应
|
||||
```
|
||||
|
||||
`config_process_at_cmd()` 当前支持:
|
||||
|
||||
- `AT`
|
||||
- `AT+?` / `AT+QUERY`
|
||||
- `AT+SAVE`
|
||||
- `AT+RESET`
|
||||
- `AT+DEFAULT`
|
||||
- `AT+MUX?` / `AT+MUX=0|1`
|
||||
- `AT+NET?` / `AT+NET=IP,MASK,GW,MAC`
|
||||
- `AT+LINK?` / `AT+LINK=ROLE,...` / `AT+LINK=ROLE`
|
||||
- `AT+BAUD?` / `AT+BAUD=U0|U1,baudrate`
|
||||
|
||||
修改网络、链路、MUX 或波特率后,代码会返回 `AT_NEED_REBOOT`,`ConfigTask` 会追加提示:`Use AT+SAVE then AT+RESET to apply changes`。
|
||||
|
||||
## 8. 业务数据流示例:无协议透传
|
||||
|
||||
本节用一个最常见场景说明数据如何流动。
|
||||
|
||||
### 8.1 场景配置
|
||||
|
||||
假设现场需要“电脑 TCP 客户端连到设备,数据直接从 `USART2` 输出;`USART2` 收到的数据再原样回到 TCP 客户端”。可以配置:
|
||||
|
||||
```text
|
||||
AT+MUX=0
|
||||
AT+LINK=S1,1,8080,0.0.0.0,0,U0
|
||||
AT+SAVE
|
||||
AT+RESET
|
||||
```
|
||||
|
||||
含义:
|
||||
|
||||
- 普通透传模式,不使用 MUX 帧。
|
||||
- 启用 `S1` TCP Server。
|
||||
- `S1` 监听本地 `8080` 端口。
|
||||
- `S1` 绑定 `U0`,也就是 `USART2`。
|
||||
|
||||
### 8.2 TCP 到 UART
|
||||
|
||||
当远端 TCP 客户端连接 `S1:8080` 并发送字节,例如 `01 02 03`:
|
||||
|
||||
```text
|
||||
远端 TCP 客户端
|
||||
-> CH390D 收包
|
||||
-> ethernetif / lwIP
|
||||
-> TcpSrvTask_S1
|
||||
-> tcp_server_worker()
|
||||
-> netconn_recv()
|
||||
-> route_send(xTcpRxQueue, src=S1, dst=UART2, data=01 02 03)
|
||||
-> UartRxTask
|
||||
-> uart_trans_send_buffer(UART_CHANNEL_U0, data)
|
||||
-> USART2 DMA TX
|
||||
-> 外部串口设备收到 01 02 03
|
||||
```
|
||||
|
||||
关键代码位置:
|
||||
|
||||
- `App/tcp_server.c`:`tcp_server_worker()` 从 `netconn_recv()` 取 `netbuf`,再投递到 `xTcpRxQueue`。
|
||||
- `App/uart_trans.c`:`UartRxTask()` 从 `xTcpRxQueue` 取消息,按 `dst_mask` 发送到 `USART2/USART3`。
|
||||
|
||||
### 8.3 UART 到 TCP
|
||||
|
||||
当外部串口设备向 `USART2` 发送 `A1 B2 C3`:
|
||||
|
||||
```text
|
||||
外部串口设备
|
||||
-> USART2 DMA RX
|
||||
-> USART2 IDLE 中断
|
||||
-> uart_trans_notify_rx_from_isr(UART_CHANNEL_U0)
|
||||
-> UartRxTask
|
||||
-> uart_route_raw_channel(U0)
|
||||
-> 查找所有 enabled 且绑定 U0 的 LINK
|
||||
-> route_send(xLinkTxQueues[S1], src=UART2, dst=S1, data=A1 B2 C3)
|
||||
-> TcpSrvTask_S1
|
||||
-> xQueueReceive(xLinkTxQueues[S1])
|
||||
-> netconn_write()
|
||||
-> lwIP / CH390D
|
||||
-> 远端 TCP 客户端收到 A1 B2 C3
|
||||
```
|
||||
|
||||
普通透传模式下,`uart_route_raw_channel()` 会把一个 UART 上收到的数据复制给所有“已启用且绑定该 UART”的链路。因此如果 `S1` 和 `C2` 都绑定 `U0` 且都启用,`USART2` 的一段输入会分别投递到 `xLinkTxQueues[S1]` 和 `xLinkTxQueues[C2]`。
|
||||
|
||||
## 9. MUX 模式数据流
|
||||
|
||||
MUX 模式由 `AT+MUX=1` 开启。帧格式在 `App/uart_trans.c` 中由 `uart_mux_try_extract_frame()` 和 `uart_mux_encode_frame()` 实现:
|
||||
|
||||
```text
|
||||
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
||||
0x7E | len high | len low | source | destinations | bytes | 0x7F
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
1. `DSTMASK=0x00`:系统控制帧,`PAYLOAD` 作为 AT 文本进入 `xConfigQueue`。
|
||||
2. `DSTMASK` 包含 `S1/S2/C1/C2`:投递到对应 `xLinkTxQueues[]`。
|
||||
3. `DSTMASK` 包含另一个 UART:编码成新的 MUX 帧并转发到另一个数据口。
|
||||
4. TCP 到 UART 时,如果目标是 UART 且当前处于 MUX 模式,会带上源端点并编码成 MUX 帧输出。
|
||||
|
||||
MUX 模式适合多个 TCP 实例共享一个数据口,或者上位机需要明确指定数据发往哪个逻辑端点的场景。
|
||||
|
||||
## 10. 网络初始化和 CH390 路径
|
||||
|
||||
网络运行入口是 `App/task_net_poll.c` 的 `NetPollTask()`:
|
||||
|
||||
1. 调用 `tcpip_init(NULL, NULL)` 创建 lwIP 内核线程。
|
||||
2. 按 `config_get()` 中的 `NET` 参数构造 `ipaddr/netmask/gateway`。
|
||||
3. 调用 `lwip_netif_init()` 初始化 netif 和 CH390 glue。
|
||||
4. 初始化成功后设置 `g_netif_ready = pdTRUE`。
|
||||
5. 调用 `app_start_network_tasks()` 创建启用的 TCP Server/Client 任务。
|
||||
6. 主循环中等待 `xNetSemaphore` 或周期轮询,驱动 `ethernetif_poll()`,并响应网络重启请求。
|
||||
|
||||
底层路径主要在:
|
||||
|
||||
- `Drivers/CH390/CH390_Interface.c`:SPI、CS、寄存器和 FIFO 访问。
|
||||
- `Drivers/CH390/CH390.c`:芯片级 helper。
|
||||
- `Drivers/LwIP/src/netif/ethernetif.c`:CH390 与 lwIP netif 的桥接。
|
||||
- `Drivers/LwIP/src/include/arch/lwipopts.h`:lwIP 池、线程、core locking 配置。
|
||||
|
||||
当前关键 lwIP 配置包括:
|
||||
|
||||
- `NO_SYS=0`
|
||||
- `LWIP_NETCONN=1`
|
||||
- `LWIP_TCPIP_CORE_LOCKING=1`
|
||||
- `LWIP_TCPIP_CORE_LOCKING_INPUT=1`
|
||||
- `PBUF_POOL_SIZE=8`
|
||||
- `MEMP_NUM_PBUF=8`
|
||||
- `MEMP_NUM_TCPIP_MSG_INPKT=8`
|
||||
- `LWIP_NETCONN_SEM_PER_THREAD=1`
|
||||
|
||||
## 11. 推荐阅读路线
|
||||
|
||||
### 11.1 只想理解系统怎么跑
|
||||
|
||||
1. `README.md`
|
||||
2. 本文第 3、4、5、8 节
|
||||
3. `Core/Src/main.c`
|
||||
4. `Core/Src/freertos.c`
|
||||
|
||||
### 11.2 要改 AT 命令或默认参数
|
||||
|
||||
1. `AT固件使用手册.md`
|
||||
2. `App/config.h`:结构体、默认值、端点编码。
|
||||
3. `App/config.c`:解析、保存、响应。
|
||||
4. `App/flash_param.c`:Flash 存储。
|
||||
|
||||
### 11.3 要改 TCP 或串口透传
|
||||
|
||||
1. 本文第 8、9 节。
|
||||
2. `App/route_msg.c`:先理解消息生命周期。
|
||||
3. `App/uart_trans.c`:UART RX/TX、普通透传、MUX。
|
||||
4. `App/tcp_server.c` 和 `App/tcp_client.c`:网络收发。
|
||||
|
||||
### 11.4 要查网络底层问题
|
||||
|
||||
1. `App/task_net_poll.c`
|
||||
2. `Drivers/LwIP/src/netif/ethernetif.c`
|
||||
3. `Drivers/CH390/CH390_Interface.c`
|
||||
4. `Drivers/LwIP/src/include/arch/lwipopts.h`
|
||||
5. RTT 日志和抓包结果一起看,不要只看单侧现象。
|
||||
|
||||
## 12. 调试指南
|
||||
|
||||
### 12.1 启动阶段
|
||||
|
||||
先看 RTT boot 日志:
|
||||
|
||||
```text
|
||||
hal-init
|
||||
clock-config
|
||||
peripherals-ready
|
||||
config-ready
|
||||
uart-trans-init
|
||||
tasks-created
|
||||
freertos-init
|
||||
scheduler-start
|
||||
```
|
||||
|
||||
如果停在 `scheduler-start` 前,优先看外设初始化、CH390 reset、RTOS 对象创建断言。如果进入任务后异常,再看 `NetPollTask`、`ConfigTask`、`UartRxTask` 的 task-entry 日志。
|
||||
|
||||
### 12.2 网络阶段
|
||||
|
||||
关键日志点:
|
||||
|
||||
- `[NET] tcpip-init enter/exit`
|
||||
- `[NET] netif-init enter/exit`
|
||||
- `[NET] post-init ok=... hwm=... free=... min=...`
|
||||
- `[NET] start-network-tasks call`
|
||||
- `[NET] netif-ready`
|
||||
|
||||
如果 netif 未 ready,不要先查 TCP 业务任务;应先查 CH390、SPI、netif 初始化和 lwIP 配置。
|
||||
|
||||
### 12.3 路由阶段
|
||||
|
||||
`route_send_result_to_str()` 的返回值很重要:
|
||||
|
||||
| 返回 | 含义 | 常见方向 |
|
||||
|------|------|----------|
|
||||
| `invalid` | 参数或长度非法 | 检查 `dst_mask`、payload 长度 |
|
||||
| `pool` | `route_msg` 固定池耗尽 | 检查消费者任务是否卡住、队列是否积压 |
|
||||
| `queue` | 目标队列满 | 检查对应 TCP/UART 任务是否还在运行 |
|
||||
|
||||
### 12.4 资源约束
|
||||
|
||||
`STM32F103RCT6` 的 SRAM 余量有限,而 FreeRTOS、lwIP、多个 TCP 任务、UART ring buffer 和消息池都会消耗 RAM。遇到 full-task 模式下的非确定性问题时,应同时记录:
|
||||
|
||||
- `xPortGetFreeHeapSize()`
|
||||
- `xPortGetMinimumEverFreeHeapSize()`
|
||||
- `uxTaskGetStackHighWaterMark()`
|
||||
- lwIP pbuf/memp 池配置
|
||||
- 哪些 `LINK` 被启用
|
||||
|
||||
如果问题随任务数量、池大小或启用链路数量明显移动,先按资源问题分析,不要急着给业务路径加补丁。
|
||||
|
||||
### 12.5 历史 CH390/lwIP pbuf 泄漏教训
|
||||
|
||||
历史上曾出现“设备能成功 ping 固定次数,随后不再回应”的问题。现象随 `PBUF_POOL_SIZE` 从 8 改到 16 而从 8 次移动到 16 次,最终定位到 lwIP 输出路径中 pbuf 引用计数未释放这一类问题。
|
||||
|
||||
这个案例的长期价值不是记住某个临时修改,而是调试方法:
|
||||
|
||||
1. 如果失败次数精确跟池大小相关,优先怀疑引用计数或释放路径。
|
||||
2. 扩大池只能延迟问题,不能当根修复。
|
||||
3. 抓包、RTT、lwIP 统计和代码引用计数要一起看。
|
||||
|
||||
## 13. 修改代码时的边界
|
||||
|
||||
1. 不要绕过 `route_msg` 和队列直接让 TCP/UART 任务互相调用。
|
||||
2. ISR 中只做通知或投递,不做阻塞等待。
|
||||
3. `netconn_*` 只在网络任务语境中使用,注意每个线程的 `netconn_thread_init()` / `netconn_thread_cleanup()`。
|
||||
4. 改 AT 命令时同步更新 `AT固件使用手册.md`。
|
||||
5. 改 MUX 帧格式或端点编码时,同步检查 `App/config.h`、`App/uart_trans.c`、`AT固件使用手册.md`。
|
||||
6. 改 lwIP 池大小时,同步记录 RAM、heap、水位和实际业务链路数量。
|
||||
|
||||
## 14. 术语速查
|
||||
|
||||
| 术语 | 含义 |
|
||||
|------|------|
|
||||
| `U0` | `USART2` 数据口 |
|
||||
| `U1` | `USART3` 数据口 |
|
||||
| `S1/S2` | TCP Server 实例 |
|
||||
| `C1/C2` | TCP Client 实例 |
|
||||
| `MUX=0` | 普通透明透传 |
|
||||
| `MUX=1` | 带 `SRCID/DSTMASK` 的帧化透传 |
|
||||
| `xTcpRxQueue` | TCP 到 UART 的队列 |
|
||||
| `xLinkTxQueues[]` | UART 到 TCP 的队列数组 |
|
||||
| `xConfigQueue` | AT 命令队列 |
|
||||
| `NetPollTask` | 网络初始化、轮询和恢复任务 |
|
||||
@@ -1,638 +0,0 @@
|
||||
# TCP2UART 项目技术实现
|
||||
|
||||
## 一、文档目的
|
||||
|
||||
本文档描述 `TCP2UART` 项目基于 `STM32F103RCT6 + FreeRTOS` 的最终内部实现口径。
|
||||
|
||||
本文档只围绕最终协议模型展开:
|
||||
|
||||
- `MUX`:串口承载层
|
||||
- `NET`:全局网络配置层
|
||||
- `LINK[idx]`:实例配置与连接管理层
|
||||
|
||||
不再保留历史 `S1... / C1...` 外部字段模型。
|
||||
|
||||
## 二、当前工程基础
|
||||
|
||||
当前工程基础约束如下:
|
||||
|
||||
1. MCU:`STM32F103RCT6`(256KB Flash / 48KB SRAM)
|
||||
2. 网络芯片:`CH390D`
|
||||
3. 软件架构:`FreeRTOS + lwIP NO_SYS=0`
|
||||
4. 协议栈:`lwIP socket/netconn API`
|
||||
5. 调试输出:`SEGGER RTT`
|
||||
6. 使用 `FreeRTOS` 任务调度
|
||||
7. 不实现 DHCP
|
||||
|
||||
## 三、总体架构
|
||||
|
||||
```text
|
||||
+--------------------------------------------------+
|
||||
| AT / Control Plane |
|
||||
| USART1 AT parser + MUX control frame parser |
|
||||
+--------------------------------------------------+
|
||||
| Configuration Model |
|
||||
| MUX / NET / LINK[idx] |
|
||||
+--------------------------------------------------+
|
||||
| FreeRTOS Tasks |
|
||||
| NetworkTask / UartTask / ConfigTask / RouteTask |
|
||||
+--------------------------------------------------+
|
||||
| Inter-Task Communication |
|
||||
| Queue / Semaphore / Mutex / StreamBuffer |
|
||||
+--------------------------------------------------+
|
||||
| lwIP TCP/IP Stack (NO_SYS=0) |
|
||||
| tcpip_thread + socket/netconn + sys_arch |
|
||||
+--------------------------------------------------+
|
||||
| Driver Layer |
|
||||
| CH390 / lwIP netif / UART DMA+IDLE / HAL |
|
||||
+--------------------------------------------------+
|
||||
```
|
||||
|
||||
## 四、FreeRTOS 任务设计(路径 A:netconn + 多 TCP 任务)
|
||||
|
||||
### 4.1 架构路线
|
||||
|
||||
本项目采用 **路径 A**:`NO_SYS=0 + netconn API + 每个 TCP 连接独立任务`。
|
||||
|
||||
核心决策:
|
||||
1. lwIP 以 `NO_SYS=0` 模式运行,`tcpip_thread` 由 lwIP 自动创建
|
||||
2. TCP 连接使用 `netconn` 阻塞 API(`netconn_accept` / `netconn_recv` / `netconn_write`)
|
||||
3. 每个 TCP Server 和 Client 实例各占一个独立任务
|
||||
4. 任务间通过 Queue 传递指针 + 元数据描述符,实现零拷贝
|
||||
|
||||
### 4.2 任务列表(共 9 个任务 + 1 个 lwIP 自建)
|
||||
|
||||
| 任务名 | 优先级 | 栈(words) | 模式 | 职责 |
|
||||
|--------|--------|-----------|------|------|
|
||||
| `tcpip_thread` | 6 (最高) | 512 | 阻塞 | lwIP 内核线程(自动创建) |
|
||||
| `NetPollTask` | 5 | 384 | 事件驱动 | `ethernetif_poll` + 链路检测 |
|
||||
| `TcpSrvTask_S1` | 4 | 384 | 阻塞 | `netconn_accept` + S1 收发 |
|
||||
| `TcpSrvTask_S2` | 4 | 384 | 阻塞 | `netconn_accept` + S2 收发 |
|
||||
| `TcpCliTask_C1` | 4 | 256 | 阻塞 | `netconn_connect` + C1 收发 |
|
||||
| `TcpCliTask_C2` | 4 | 256 | 阻塞 | `netconn_connect` + C2 收发 |
|
||||
| `UartRxTask` | 4 | 384 | 事件驱动 | UART DMA/IDLE 接收 + MUX 帧提取 |
|
||||
| `ConfigTask` | 2 | 256 | 阻塞 | AT 命令解析与响应 |
|
||||
| `DefaultTask` | 1 | 128 | 周期 | LED 心跳 + IWDG 喂狗 |
|
||||
|
||||
说明:
|
||||
- `tcpip_thread` 是 lwIP 自建的内核线程,处理所有协议栈内部事件
|
||||
- 所有 `netconn_*` API 通过 `tcpip_thread` 消息机制实现线程安全
|
||||
- `LWIP_TCPIP_CORE_LOCKING=1` 允许应用任务直接调用 `netconn_write` 而不经邮箱中转
|
||||
- `tcpip_thread` 优先级最高(6),确保 TCP ACK 和超时处理不被延迟
|
||||
|
||||
### 4.3 零拷贝路由消息设计
|
||||
|
||||
```c
|
||||
/* 路由消息描述符 - Queue 传递的是此结构的指针,不拷贝负载数据 */
|
||||
typedef struct {
|
||||
uint8_t src_id; /* 源端点 ID */
|
||||
uint8_t dst_mask; /* 目标端点位图 */
|
||||
uint16_t len; /* 数据长度 */
|
||||
uint8_t conn_type; /* 连接标识:LINK_S1/S2/C1/C2 */
|
||||
uint8_t *data; /* 指向预分配静态缓冲区 */
|
||||
} route_msg_t;
|
||||
```
|
||||
|
||||
静态缓冲池(预分配,避免动态分配):
|
||||
|
||||
```c
|
||||
#define ROUTE_BUF_COUNT 4
|
||||
#define ROUTE_BUF_SIZE 512
|
||||
|
||||
static uint8_t g_route_buf_pool[ROUTE_BUF_COUNT][ROUTE_BUF_SIZE];
|
||||
static volatile uint8_t g_route_buf_used[ROUTE_BUF_COUNT];
|
||||
```
|
||||
|
||||
- 发送方:从池中获取空闲缓冲区,拷贝数据,填充 `route_msg_t`,Queue 发送指针
|
||||
- 接收方:从 Queue 取 `route_msg_t*`,处理数据后标记缓冲区为可用
|
||||
- 无动态分配,无堆碎片
|
||||
|
||||
### 4.4 任务间通信机制
|
||||
|
||||
```text
|
||||
UART ISR ──[TaskNotify]──> UartRxTask ──[Queue*]──> TcpSrvTask / TcpCliTask
|
||||
│ ▲
|
||||
├──[Queue*]──────────────>─┘
|
||||
│
|
||||
DSTMASK=0 └──[Queue]──> ConfigTask
|
||||
|
||||
TcpSrvTask / TcpCliTask ──[Queue*]──> UartRxTask (UART TX 方向)
|
||||
|
||||
EXTI0 ISR ──[BinarySem]──> NetPollTask ──> ethernetif_poll ──> tcpip_thread
|
||||
```
|
||||
|
||||
通信对象:
|
||||
|
||||
| 对象 | 类型 | 生产者 | 消费者 | 用途 |
|
||||
|------|------|--------|--------|------|
|
||||
| `xNetSemaphore` | Binary Semaphore | EXTI0 ISR | NetPollTask | CH390 中断通知 |
|
||||
| `xUartRxNotify` | TaskNotification | UART IDLE ISR | UartRxTask | UART 接收通知 |
|
||||
| `xTcpRxQueue` | Queue (16, route_msg_t*) | TCP 任务 | UartRxTask | TCP→UART 数据 |
|
||||
| `xUartTxQueue` | Queue (8, route_msg_t*) | UartRxTask | TCP 任务 | UART→TCP 数据 |
|
||||
| `xConfigQueue` | Queue (8, char*) | UartRxTask | ConfigTask | AT 文本 |
|
||||
|
||||
说明:
|
||||
- TCP→UART 方向:TCP 任务调用 `netconn_recv` 获取数据,构造 `route_msg_t` 指针投递到 `xTcpRxQueue`,UartRxTask 取出后写入 UART DMA
|
||||
- UART→TCP 方向:UartRxTask 从 UART DMA 读取数据,构造 `route_msg_t` 指针投递到 `xUartTxQueue`,对应 TCP 任务取出后调用 `netconn_write` 发送
|
||||
- 路由逻辑内联在 UartRxTask 和各 TCP 任务中,不设独立 RouteTask
|
||||
|
||||
### 4.5 NetPollTask 实现
|
||||
|
||||
```c
|
||||
void NetPollTask(void *argument)
|
||||
{
|
||||
/* 初始化 CH390 + lwIP netif */
|
||||
ethernetif_init(&ch390_netif);
|
||||
/* 等待链路就绪后启动 TCP 任务 */
|
||||
/* ... */
|
||||
|
||||
for (;;) {
|
||||
xSemaphoreTake(xNetSemaphore, pdMS_TO_TICKS(2));
|
||||
ethernetif_poll(&ch390_netif);
|
||||
ethernetif_check_link(&ch390_netif);
|
||||
/* sys_check_timeouts() 由 tcpip_thread 自动执行,此处不需要调用 */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6 TcpSrvTask 实现模板
|
||||
|
||||
```c
|
||||
void TcpSrvTask_S1(void *argument)
|
||||
{
|
||||
struct netconn *conn = netconn_new(NETCONN_TCP);
|
||||
netconn_bind(conn, IP_ADDR_ANY, cfg->links[0].lport);
|
||||
netconn_listen(conn);
|
||||
|
||||
for (;;) {
|
||||
struct netconn *newconn;
|
||||
if (netconn_accept(conn, &newconn) == ERR_OK) {
|
||||
/* 在本任务内处理唯一客户端 */
|
||||
tcp_server_worker(newconn, LINK_S1);
|
||||
netconn_close(newconn);
|
||||
netconn_delete(newconn);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.7 TcpCliTask 实现模板
|
||||
|
||||
```c
|
||||
void TcpCliTask_C1(void *argument)
|
||||
{
|
||||
for (;;) {
|
||||
struct netconn *conn = netconn_new(NETCONN_TCP);
|
||||
ip_addr_t remote_ip;
|
||||
IP_ADDR4(&remote_ip, cfg->links[2].rip[0], ...);
|
||||
|
||||
if (netconn_connect(conn, &remote_ip, cfg->links[2].rport) == ERR_OK) {
|
||||
tcp_client_worker(conn, LINK_C1);
|
||||
}
|
||||
netconn_close(conn);
|
||||
netconn_delete(conn);
|
||||
vTaskDelay(pdMS_TO_TICKS(cfg->links[2].reconnect_interval));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.8 UartRxTask 实现
|
||||
|
||||
```c
|
||||
void UartRxTask(void *argument)
|
||||
{
|
||||
for (;;) {
|
||||
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10));
|
||||
/* 处理 UART2/UART3 DMA 接收数据 */
|
||||
/* MUX=0: 构造 route_msg_t 指针投递到 xUartTxQueue */
|
||||
/* MUX=1: 提取 MUX 帧,DSTMASK=0 投 xConfigQueue,否则投 xUartTxQueue */
|
||||
/* 从 xTcpRxQueue 取数据,写入 UART DMA 发送 */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.9 ConfigTask 实现
|
||||
|
||||
```c
|
||||
void ConfigTask(void *argument)
|
||||
{
|
||||
for (;;) {
|
||||
char *cmd;
|
||||
xQueueReceive(xConfigQueue, &cmd, portMAX_DELAY);
|
||||
config_process_at_cmd(cmd);
|
||||
/* 通过 UART1 发送响应 */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 五、最终协议实现模型
|
||||
|
||||
### 5.1 MUX 帧承载层
|
||||
|
||||
数据口启用 MUX 后,统一处理如下帧:
|
||||
|
||||
```text
|
||||
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
||||
```
|
||||
|
||||
实现职责:
|
||||
|
||||
1. 识别帧边界
|
||||
2. 解析长度字段
|
||||
3. 提取 `SRCID`
|
||||
4. 解析 `DSTMASK`
|
||||
5. 按控制帧或数据帧分流
|
||||
|
||||
### 5.2 控制帧与数据帧分离
|
||||
|
||||
控制规则固定如下:
|
||||
|
||||
- `DSTMASK = 0x00`:系统控制帧
|
||||
- `DSTMASK != 0x00`:业务数据帧
|
||||
|
||||
系统控制帧处理要求:
|
||||
|
||||
1. `PAYLOAD` 解释为 AT 文本
|
||||
2. AT 文本必须以 `\r\n` 结束
|
||||
3. 控制帧投递到 `ConfigTask`
|
||||
|
||||
业务数据帧处理要求:
|
||||
|
||||
1. `SRCID` 表示单一源端点
|
||||
2. `DSTMASK` 表示目标端点集合
|
||||
3. `RouteTask` 根据 `DSTMASK` 做多目标分发
|
||||
|
||||
### 5.3 统一端点编码
|
||||
|
||||
内部与外部文档统一使用以下端点编码:
|
||||
|
||||
| 端点 | 编码 |
|
||||
|------|------|
|
||||
| `C1` | `0x01` |
|
||||
| `C2` | `0x02` |
|
||||
| `UART2` | `0x04` |
|
||||
| `UART3` | `0x08` |
|
||||
| `S1` | `0x10` |
|
||||
| `S2` | `0x20` |
|
||||
|
||||
实现要求:
|
||||
|
||||
- `SRCID` 为单值
|
||||
- `DSTMASK` 为位图
|
||||
- `DSTMASK=0x00` 仅保留为控制帧
|
||||
|
||||
## 六、配置层设计
|
||||
|
||||
### 6.1 MUX 记录
|
||||
|
||||
`MUX` 为全局记录,仅控制设备数据口是否进入 MUX 承载模式。
|
||||
|
||||
取值:
|
||||
|
||||
- `0`:普通透传
|
||||
- `1`:MUX 透传
|
||||
|
||||
### 6.2 NET 记录
|
||||
|
||||
`NET` 为全局静态网络记录:
|
||||
|
||||
```text
|
||||
IP,MASK,GW,MAC
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 设备只有一张网卡,因此不为每个实例单独配置本地 IP
|
||||
- 当前实现目标中不包含 DHCP
|
||||
|
||||
### 6.3 LINK 记录
|
||||
|
||||
`LINK[idx]` 为统一实例记录:
|
||||
|
||||
```text
|
||||
EN,LPORT,RIP,RPORT,UART
|
||||
```
|
||||
|
||||
固定索引映射:
|
||||
|
||||
- `0 = S1`
|
||||
- `1 = S2`
|
||||
- `2 = C1`
|
||||
- `3 = C2`
|
||||
|
||||
字段职责:
|
||||
|
||||
- `EN`:实例启用状态
|
||||
- `LPORT`:本地端口
|
||||
- `RIP / RPORT`:对端地址与端口
|
||||
- `UART`:对应业务数据口
|
||||
|
||||
说明:
|
||||
|
||||
- `Server` 与 `Client` 共享同一记录结构
|
||||
- `Server` 的 `RIP / RPORT` 可作为对端约束或预设
|
||||
- `Client` 的 `RIP / RPORT` 表示远端目标
|
||||
|
||||
## 七、模块职责
|
||||
|
||||
### 7.1 配置模块 `config.c/.h`
|
||||
|
||||
最终职责:
|
||||
|
||||
1. 解析 `AT+MUX`
|
||||
2. 解析 `AT+NET`
|
||||
3. 解析 `AT+LINK`
|
||||
4. 加载与保存配置
|
||||
5. 处理 `SAVE / RESET / DEFAULT`
|
||||
|
||||
### 7.2 UART 透传模块 `uart_trans.c/.h`
|
||||
|
||||
最终职责:
|
||||
|
||||
1. 保持 `USART2 / USART3` 的 `DMA + IDLE` 接收发送基线
|
||||
2. 在 `MUX=0` 时执行普通透传
|
||||
3. 在 `MUX=1` 时执行 MUX 帧收发
|
||||
4. 将控制帧与业务数据帧分流
|
||||
|
||||
### 7.3 TCP Server / Client 模块(需重写)
|
||||
|
||||
原 `tcp_server.c` / `tcp_client.c` 基于 lwIP RAW API 回调模式,需重写为 netconn 阻塞模式:
|
||||
|
||||
1. 删除所有 `tcp_pcb` / `tcp_recv` / `tcp_accept` 回调代码
|
||||
2. 改用 `netconn_new` / `netconn_bind` / `netconn_listen` / `netconn_accept`(Server)
|
||||
3. 改用 `netconn_new` / `netconn_connect`(Client)
|
||||
4. 收发改为 `netconn_recv` / `netconn_write` 阻塞调用
|
||||
5. 内部 ring buffer 可取消(netconn 内部已有 pbuf 缓冲)
|
||||
6. 每个 TCP 任务内直接处理路由,通过 Queue 指针传递数据
|
||||
|
||||
### 7.4 FreeRTOS 初始化 `freertos.c`
|
||||
|
||||
CubeMX 生成的 FreeRTOS 初始化文件,职责:
|
||||
|
||||
1. 定义默认任务 `StartDefaultTask`
|
||||
2. 用户在 `MX_FREERTOS_Init` 中创建自定义任务
|
||||
|
||||
### 7.5 FreeRTOS 配置 `FreeRTOSConfig.h`
|
||||
|
||||
关键配置项:
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| `configUSE_PREEMPTION` | 1 | 抢占式调度 |
|
||||
| `configTICK_RATE_HZ` | 1000 | 1ms tick |
|
||||
| `configMINIMAL_STACK_SIZE` | 128 | 最小栈(words) |
|
||||
| `configTOTAL_HEAP_SIZE` | 10240 | FreeRTOS 堆大小 |
|
||||
| `configMAX_PRIORITIES` | 7 | 最大优先级数 |
|
||||
| `configUSE_MUTEXES` | 1 | 启用互斥锁 |
|
||||
| `configUSE_COUNTING_SEMAPHORES` | 1 | 启用计数信号量 |
|
||||
| `configUSE_RECURSIVE_MUTEXES` | 1 | 启用递归互斥锁 |
|
||||
| `configCHECK_FOR_STACK_OVERFLOW` | 2 | 栈溢出检测方式 2 |
|
||||
| `configUSE_MALLOC_FAILED_HOOK` | 1 | 内存分配失败钩子 |
|
||||
| `configSUPPORT_DYNAMIC_ALLOCATION` | 1 | 动态内存分配 |
|
||||
|
||||
## 八、lwIP 配置(NO_SYS=0 + netconn)
|
||||
|
||||
### 8.1 lwIP 线程模型
|
||||
|
||||
由于采用 `NO_SYS=0`,lwIP 将运行以下线程:
|
||||
|
||||
1. `tcpip_thread`(优先级 6,栈 512 words):lwIP 核心线程,处理所有协议栈内部事件
|
||||
2. 应用任务通过 `netconn` API 与 lwIP 交互,由 `tcpip_thread` 消息机制保证线程安全
|
||||
3. `LWIP_TCPIP_CORE_LOCKING=1`:允许应用任务在持有核心锁时直接调用 `netconn_write`,无需通过邮箱中转
|
||||
|
||||
### 8.2 lwIP 关键配置项(lwipopts.h)
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| `NO_SYS` | 0 | 启用 OS 抽象 |
|
||||
| `LWIP_NETCONN` | 1 | 启用 netconn API |
|
||||
| `LWIP_SOCKET` | 0 | 不使用 socket API,节省 RAM |
|
||||
| `LWIP_TCPIP_CORE_LOCKING` | 1 | 允许直接发送,减少延时 |
|
||||
| `MEM_SIZE` | 8192 | lwIP 堆大小 |
|
||||
| `PBUF_POOL_SIZE` | 10 | pbuf 池数量 |
|
||||
| `MEMP_NUM_NETCONN` | 8 | 2监听 + 4连接 + 2余量 |
|
||||
| `MEMP_NUM_NETBUF` | 8 | netconn 缓冲 |
|
||||
| `MEMP_NUM_TCP_PCB` | 4 | TCP 控制块 |
|
||||
| `MEMP_NUM_TCP_PCB_LISTEN` | 2 | TCP 监听 |
|
||||
| `MEMP_NUM_TCP_SEG` | 24 | TCP 段 |
|
||||
| `MEMP_NUM_TCPIP_MSG_API` | 8 | API 消息池 |
|
||||
| `MEMP_NUM_TCPIP_MSG_INPKT` | 8 | 入包消息池 |
|
||||
| `TCP_MSS` | 536 | 保守 MSS |
|
||||
| `TCP_SND_BUF` | 8×MSS=4288 | 发送缓冲 |
|
||||
| `TCP_WND` | 8×MSS=4288 | 接收窗口 |
|
||||
| `TCPIP_THREAD_STACKSIZE` | 512 | tcpip_thread 栈 |
|
||||
| `TCPIP_THREAD_PRIO` | 6 (最高) | tcpip_thread 优先级 |
|
||||
| `LWIP_DHCP` | 0 | 不使用 DHCP |
|
||||
| `LWIP_UDP` | 0 | 不使用 UDP |
|
||||
|
||||
### 8.3 sys_arch 移植层
|
||||
|
||||
`Drivers/LwIP/port/sys_arch.c` 需实现 lwIP 到 FreeRTOS 的适配:
|
||||
|
||||
1. `sys_thread_new`:创建 lwIP 线程(即 `tcpip_thread`)
|
||||
2. `sys_mbox_*`:消息邮箱(基于 FreeRTOS Queue,容量 `TCPIP_MBOX_SIZE=8`)
|
||||
3. `sys_sem_*`:信号量(基于 FreeRTOS Semaphore)
|
||||
4. `sys_mutex_*`:互斥锁(基于 FreeRTOS Mutex)
|
||||
5. `sys_arch_protect / unprotect`:临界区保护(基于 `vPortEnterCritical / vPortExitCritical`)
|
||||
|
||||
### 8.4 lwIP 初始化流程
|
||||
|
||||
```c
|
||||
/* 在 MX_FREERTOS_Init 或 NetPollTask 中调用 */
|
||||
void lwip_init_task(void)
|
||||
{
|
||||
/* tcpip_thread 在 lwip_init() 或第一个 sys_thread_new 时自动启动 */
|
||||
tcpip_init(NULL, NULL);
|
||||
|
||||
/* 等待 tcpip_thread 就绪 */
|
||||
/* 添加网络接口 */
|
||||
netif_add(&ch390_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
|
||||
netif_set_default(&ch390_netif);
|
||||
netif_set_up(&ch390_netif);
|
||||
}
|
||||
```
|
||||
|
||||
## 九、中断与 HAL 时间基准
|
||||
|
||||
### 9.1 HAL 时间基准
|
||||
|
||||
FreeRTOS 下 `SysTick` 被 FreeRTOS 占用,HAL 时间基准改用 `TIM4`:
|
||||
|
||||
- `TIM4` 配置为 1ms 中断(72MHz / (71+1) / (999+1) = 1kHz)
|
||||
- `HAL_InitTick` 使用 `TIM4` 而非 `SysTick`
|
||||
- `uwTick` 在 `TIM4_IRQHandler` 中递增
|
||||
|
||||
### 9.2 中断优先级规划
|
||||
|
||||
| 中断 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| `SysTick` | 15(最低) | FreeRTOS tick |
|
||||
| `PendSV` | 15(最低) | FreeRTOS 上下文切换 |
|
||||
| `SVCall` | 0 | FreeRTOS 服务调用 |
|
||||
| `TIM4` | 0 | HAL 时间基准 |
|
||||
| `EXTI0` | 5 | CH390 中断 |
|
||||
| `DMA1_Ch2~7` | 5 | UART DMA |
|
||||
| `USART1/2/3` | 5 | UART 中断 |
|
||||
| `SPI1` | 5 | SPI 中断 |
|
||||
|
||||
FreeRTOS 可管理的中断优先级必须 >= `configMAX_SYSCALL_INTERRUPT_PRIORITY`(本工程为 5)。
|
||||
|
||||
## 十、内存预算(路径 A 精确估算)
|
||||
|
||||
以 `STM32F103RCT6` 为目标(48KB SRAM):
|
||||
|
||||
### 10.1 RAM 预算
|
||||
|
||||
| 项目 | 大小 | 说明 |
|
||||
|------|------|------|
|
||||
| 启动栈 (MSP) | 2,048 B | `startup_stm32f103xe.s` 定义 0x800 |
|
||||
| FreeRTOS 堆 (heap_4) | 10,240 B | `configTOTAL_HEAP_SIZE` |
|
||||
| 任务栈 (9 任务) | 13,312 B | 3,328 words × 4 (见上表) |
|
||||
| lwIP 堆 (MEM_SIZE) | 8,192 B | |
|
||||
| PBUF 池 (10 个) | ~6,000 B | 10 × ~600B |
|
||||
| MEMP 池 | ~4,000 B | netconn/netbuf/tcpip_msg/pcb/seg |
|
||||
| UART DMA+Ring 缓冲 | 2,304 B | 2 通道 × (256+256+512+384)/2 |
|
||||
| 路由缓冲池 (4×512B) | 2,048 B | 零拷贝指针传递 |
|
||||
| 配置结构 | 1,024 B | |
|
||||
| **合计** | **~49,168 B** | |
|
||||
| **余量 (RCT6 48KB)** | **~-928 B** | ⚠️ 超出,需优化或换 RDT6 |
|
||||
| **余量 (RDT6 64KB)** | **~15,264 B** | ✅ 充裕 |
|
||||
|
||||
### 10.2 优化空间(RCT6 下)
|
||||
|
||||
若坚持使用 RCT6,可通过以下措施压缩到 48KB 以内:
|
||||
|
||||
| 优化项 | 节省 |
|
||||
|--------|------|
|
||||
| 取消 TCP 任务的 ring buffer(netconn 内部有 pbuf 缓冲) | -2,048 B |
|
||||
| `configTOTAL_HEAP_SIZE` 降至 8KB | -2,048 B |
|
||||
| `MEM_SIZE` 降至 6KB | -2,048 B |
|
||||
| TcpCliTask 栈降至 192 words × 2 | -512 B |
|
||||
| **优化后合计** | **~42,504 B** |
|
||||
| **RCT6 余量** | **~5,464 B** |
|
||||
|
||||
### 10.3 备选 MCU
|
||||
|
||||
当 RAM 最终不够用时,切换为 `STM32F103RDT6`(pin-to-pin 替代):
|
||||
|
||||
| 项目 | RCT6 | RDT6 |
|
||||
|------|------|------|
|
||||
| Flash | 256 KB | 384 KB |
|
||||
| SRAM | 48 KB | 64 KB |
|
||||
| 引脚 | LQFP64 | LQFP64(完全兼容) |
|
||||
| 启动文件 | `startup_stm32f103xe.s` | `startup_stm32f103xe.s` |
|
||||
| 宏定义 | `STM32F103xE` | `STM32F103xE` |
|
||||
| Flash 算法 | `STM32F10x_HD` | `STM32F10x_HD`(相同) |
|
||||
| SRAM 大小 | `0xC000` | `0x10000` |
|
||||
| Flash 大小 | `0x40000` | `0x60000` |
|
||||
|
||||
### 10.4 Flash 预算
|
||||
|
||||
| 项目 | 估计值 | 说明 |
|
||||
|------|--------|------|
|
||||
| FreeRTOS 内核 | ~8 KB | 含 CMSIS-RTOS V2 |
|
||||
| HAL 驱动 | ~20 KB | GPIO/UART/SPI/DMA/IWDG/TIM |
|
||||
| lwIP 协议栈 | ~50 KB | core + api + ipv4 + netif + sys_arch |
|
||||
| CH390 驱动 | ~4 KB | |
|
||||
| 应用代码 | ~20 KB | config/uart_trans/tcp_server/tcp_client |
|
||||
| **合计** | ~102 KB | RCT6 预留 154 KB,RDT6 预留 282 KB |
|
||||
|
||||
## 十一、硬件资源
|
||||
|
||||
### 11.1 MCU
|
||||
|
||||
- 型号:`STM32F103RCT6`
|
||||
- Flash:`256 KB`
|
||||
- SRAM:`48 KB`
|
||||
- 主频:`72 MHz`
|
||||
|
||||
### 11.2 主要外设
|
||||
|
||||
- `SPI1`:连接 `CH390D`
|
||||
- `USART1`:配置串口
|
||||
- `USART2`:数据透传串口
|
||||
- `USART3`:数据透传串口
|
||||
- `DMA1`:3 路 UART 收发 DMA
|
||||
- `EXTI0`:CH390 中断输入
|
||||
- `IWDG`:独立看门狗
|
||||
- `TIM4`:HAL 时间基准(替代 SysTick)
|
||||
|
||||
### 11.3 引脚分配
|
||||
|
||||
| 引脚 | 功能 | 用途 |
|
||||
|------|------|------|
|
||||
| PA2 | USART2_TX | 数据透传串口 |
|
||||
| PA3 | USART2_RX | 数据透传串口 |
|
||||
| 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 | 配置串口 |
|
||||
| PB0 | EXTI0 | CH390D INT |
|
||||
| PB1 | GPIO_Output | CH390D RESET |
|
||||
| PB10 | USART3_TX | 数据透传串口 |
|
||||
| PB11 | USART3_RX | 数据透传串口 |
|
||||
| PC13 | GPIO_Output | 状态 LED |
|
||||
| PD0/PD1 | HSE | 8MHz 外部晶振 |
|
||||
|
||||
## 十二、实现边界
|
||||
|
||||
1. 保持单网卡静态网络模型
|
||||
2. 不实现 DHCP
|
||||
3. 不实现旧 `S1... / C1...` 外部协议字段
|
||||
4. 不在文档中保留兼容层描述
|
||||
5. 所有 AT 文本控制统一要求 `\r\n` 结束
|
||||
6. FreeRTOS 堆管理使用 `heap_4.c`
|
||||
7. HAL 时间基准使用 `TIM4` 而非 `SysTick`
|
||||
|
||||
## 十三、文档一致性要求
|
||||
|
||||
后续实现、联调、测试与代码注释必须遵守以下统一口径:
|
||||
|
||||
1. 对外协议只使用 `MUX / NET / LINK`
|
||||
2. 控制帧只使用 `DSTMASK=0x00`
|
||||
3. MUX 帧格式固定为 `SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL`
|
||||
4. AT 手册、需求说明、技术实现三份文档不得再出现历史展开式字段
|
||||
|
||||
## 十四、路径 A 实现清单
|
||||
|
||||
### 14.1 必须重写的模块
|
||||
|
||||
| 模块 | 原实现 | 目标实现 | 说明 |
|
||||
|------|--------|----------|------|
|
||||
| `tcp_server.c/.h` | lwIP RAW API 回调 | netconn 阻塞任务 | `tcp_new` → `netconn_new`,回调 → 阻塞循环 |
|
||||
| `tcp_client.c/.h` | lwIP RAW API 回调 | netconn 阻塞任务 | 同上 |
|
||||
| `sys_arch.c/.h` | NO_SYS=1 空壳 | FreeRTOS 移植层 | `sys_mbox`、`sys_sem`、`sys_mutex`、`sys_thread` |
|
||||
| `lwipopts.h` | NO_SYS=1 | NO_SYS=0 + netconn | 已更新 |
|
||||
| `main.c` | while(1) 轮询 | FreeRTOS 任务创建 | `App_Poll()` → 各任务函数 |
|
||||
| `stm32f1xx_it.c` | 裸机 ISR | FreeRTOS ISR | `xxFromISR` API |
|
||||
|
||||
### 14.2 可复用(需适配)的模块
|
||||
|
||||
| 模块 | 适配内容 |
|
||||
|------|----------|
|
||||
| `uart_trans.c/.h` | 添加 `xTaskNotifyFromISR` 替代 poll 模式 |
|
||||
| `config.c/.h` | 添加 `xQueueReceive` 替代 `config_poll()` |
|
||||
| `flash_param.c/.h` | 无需修改 |
|
||||
| `CH390` 驱动 | `ethernetif_poll` 改为 NetPollTask 调用,SPI 加 Mutex 保护 |
|
||||
| `ethernetif.c` | 添加 `netif_add` 的 `tcpip_input` 回调 |
|
||||
|
||||
### 14.3 无需修改的模块
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| `FreeRTOSConfig.h` | 已更新(任务优先级宏、堆大小) |
|
||||
| `startup_stm32f103xe.s` | 已适配 RCT6 |
|
||||
| `TCP2UART.ioc` | 已适配 RCT6 + FreeRTOS + TIM4 |
|
||||
| `MDK-ARM/TCP2UART.uvprojx` | 已适配 RCT6 + xE 宏 |
|
||||
| MUX 帧编解码 | 协议逻辑与 RTOS 无关 |
|
||||
|
||||
### 14.4 新增模块
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| `route_msg.c/.h` | 零拷贝路由消息池管理 |
|
||||
| `task_tcp_server.c/.h` | netconn Server 任务模板 |
|
||||
| `task_tcp_client.c/.h` | netconn Client 任务模板 |
|
||||
| `task_net_poll.c/.h` | CH390 poll + link check 任务 |
|
||||
@@ -1,191 +0,0 @@
|
||||
# TCP2UART 项目需求说明
|
||||
|
||||
## 一、项目目标
|
||||
|
||||
本项目基于 `STM32F103RCT6` 与 `CH390D` 实现一台多实例 TCP 与双串口数据透传设备。
|
||||
|
||||
最终对外协议模型固定为:
|
||||
|
||||
1. `MUX`:控制串口侧是否采用 MUX 承载
|
||||
2. `NET`:全局静态网络配置
|
||||
3. `LINK[idx]`:按实例索引组织的链路配置
|
||||
|
||||
系统必须支持:
|
||||
|
||||
- `2` 路 TCP Server 实例
|
||||
- `2` 路 TCP Client 实例
|
||||
- `UART1` 作为 AT 配置口
|
||||
- `UART2 / UART3` 作为业务数据口
|
||||
|
||||
## 二、硬件与软件边界
|
||||
|
||||
### 2.1 硬件边界
|
||||
|
||||
- 主控:`STM32F103RCT6`(256KB Flash / 48KB SRAM)
|
||||
- 备选主控:`STM32F103RDT6`(384KB Flash / 64KB SRAM),pin-to-pin 兼容,当 RAM 不够时直接替换
|
||||
- 以太网芯片:`CH390D`
|
||||
- 网卡数量:`1`
|
||||
- 配置口:`UART1`
|
||||
- 数据口:`UART2`、`UART3`
|
||||
|
||||
### 2.2 软件边界
|
||||
|
||||
- 执行模型:`FreeRTOS`
|
||||
- 网络协议栈:`lwIP NO_SYS=0 + netconn API`(线程安全,每连接独立任务)
|
||||
- 调试输出:`SEGGER RTT`
|
||||
- 采用 `FreeRTOS` 任务调度
|
||||
- TCP 连接使用 `netconn` 阻塞 API(`netconn_accept` / `netconn_recv` / `netconn_write`)
|
||||
- 每条 TCP 连路(S1/S2/C1/C2)独立一个任务
|
||||
- 不包含 DHCP 协议支持
|
||||
|
||||
## 三、最终协议需求
|
||||
|
||||
### 3.1 MUX 帧格式
|
||||
|
||||
所有 MUX 数据承载必须使用如下格式:
|
||||
|
||||
```text
|
||||
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- `DSTMASK != 0x00`:业务数据帧
|
||||
- `DSTMASK = 0x00`:系统控制帧
|
||||
- 系统控制帧承载 AT 文本命令
|
||||
- AT 文本命令必须以 `\r\n` 结尾
|
||||
|
||||
### 3.2 统一端点编码
|
||||
|
||||
系统必须使用统一端点编码,同时覆盖 UART 与 TCP 逻辑实例:
|
||||
|
||||
| 端点 | 编码 |
|
||||
|------|------|
|
||||
| `C1` | `0x01` |
|
||||
| `C2` | `0x02` |
|
||||
| `UART2` | `0x04` |
|
||||
| `UART3` | `0x08` |
|
||||
| `S1` | `0x10` |
|
||||
| `S2` | `0x20` |
|
||||
|
||||
要求:
|
||||
|
||||
- `SRCID` 为单值
|
||||
- `DSTMASK` 为位图
|
||||
- `DSTMASK=0x00` 仅保留给系统控制帧
|
||||
|
||||
## 四、AT 接口需求
|
||||
|
||||
### 4.1 命令分类
|
||||
|
||||
AT 协议必须收敛为以下三类命令:
|
||||
|
||||
1. `AT+MUX`
|
||||
2. `AT+NET`
|
||||
3. `AT+LINK`
|
||||
|
||||
不再保留历史展开式实例字段命令。
|
||||
|
||||
### 4.2 MUX 命令需求
|
||||
|
||||
- `AT+MUX=0/1`:设置全局 MUX 模式
|
||||
- `AT+MUX?`:查询当前 MUX 模式
|
||||
|
||||
### 4.3 NET 命令需求
|
||||
|
||||
`NET` 必须统一表达以下静态网络参数:
|
||||
|
||||
```text
|
||||
IP,MASK,GW,MAC
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 设备只有一张网卡,因此本地 IP 不按实例拆分
|
||||
- DHCP 不属于协议需求范围
|
||||
|
||||
### 4.4 LINK 命令需求
|
||||
|
||||
`LINK[idx]` 必须统一表达如下字段:
|
||||
|
||||
```text
|
||||
EN,LPORT,RIP,RPORT,UART
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- `idx` 固定映射四个实例:`0=S1`、`1=S2`、`2=C1`、`3=C2`
|
||||
- `Server` 与 `Client` 共用同一条 `LINK` 配置模型
|
||||
- `LPORT` 必须可配置
|
||||
- `RIP / RPORT` 必须可配置
|
||||
- `UART` 必须可配置
|
||||
|
||||
## 五、功能需求
|
||||
|
||||
### 5.1 TCP 功能
|
||||
|
||||
- 支持 `2` 路 Server
|
||||
- 支持 `2` 路 Client
|
||||
- 每个实例通过 `LINK[idx]` 配置其本地端口、对端地址、对端端口和串口路由
|
||||
|
||||
### 5.2 串口透传功能
|
||||
|
||||
- `UART2 / UART3` 支持普通透传模式与 MUX 透传模式
|
||||
- 当需要多实例共享数据口时,必须启用 MUX 模式
|
||||
- 业务数据流向由 `SRCID / DSTMASK` 决定
|
||||
|
||||
### 5.3 系统控制功能
|
||||
|
||||
- 系统控制帧由 `DSTMASK=0x00` 表示
|
||||
- 系统控制帧进入 AT 解析路径
|
||||
- 控制文本必须以 `\r\n` 结束
|
||||
|
||||
### 5.4 参数保存功能
|
||||
|
||||
- 参数修改后支持 `SAVE`
|
||||
- 支持 `RESET` 后按保存配置启动
|
||||
- 支持恢复默认配置
|
||||
|
||||
## 六、FreeRTOS 任务架构需求
|
||||
|
||||
### 6.1 任务划分
|
||||
|
||||
系统至少应包含以下 FreeRTOS 任务:
|
||||
|
||||
| 任务 | 优先级 | 职责 |
|
||||
|------|--------|------|
|
||||
| tcpip_thread | 6 (最高) | lwIP 内核线程(自动创建) |
|
||||
| NetPollTask | 5 | CH390 事件轮询 + 链路检测 |
|
||||
| TcpSrvTask_S1 | 4 | S1 netconn_accept + 收发 |
|
||||
| TcpSrvTask_S2 | 4 | S2 netconn_accept + 收发 |
|
||||
| TcpCliTask_C1 | 4 | C1 netconn_connect + 收发 |
|
||||
| TcpCliTask_C2 | 4 | C2 netconn_connect + 收发 |
|
||||
| UartRxTask | 4 | UART DMA/IDLE 接收 + MUX 帧提取 + 路由 |
|
||||
| ConfigTask | 2 | AT 命令解析与响应 |
|
||||
| DefaultTask | 1 | LED 心跳 + 看门狗 |
|
||||
|
||||
### 6.2 任务间通信
|
||||
|
||||
- 使用 `Queue` 传递指针 + 元数据描述符(零拷贝路由消息)
|
||||
- 使用 `Binary Semaphore` 同步 CH390 中断事件
|
||||
- 使用 `TaskNotification` 通知 UART IDLE 事件
|
||||
- 预分配静态缓冲池,避免动态分配
|
||||
|
||||
## 七、非功能需求
|
||||
|
||||
1. 满足 `STM32F103RCT6` 的 `256KB Flash / 48KB SRAM` 约束,若 RAM 不足可切换 `STM32F103RDT6`(pin-to-pin,64KB SRAM)
|
||||
2. 工程可在 `MDK-ARM` 下构建
|
||||
3. 调试输出统一使用 `SEGGER RTT`
|
||||
4. 不引入 DHCP、DNS、UDP 等当前非目标协议
|
||||
5. FreeRTOS 堆使用 `heap_4.c`,总堆大小建议 `10KB`
|
||||
6. 所有任务栈通过 `uxTaskGetStackHighWaterMark` 监控
|
||||
|
||||
## 八、验收口径
|
||||
|
||||
验收时以下几点必须同时成立:
|
||||
|
||||
1. 文档只使用 `MUX / NET / LINK` 作为最终协议模型
|
||||
2. 文档不再出现历史 `S1... / C1...` 外部字段
|
||||
3. 串口控制文本统一规定为 `\r\n` 结束
|
||||
4. MUX 帧格式与端点编码在需求、手册、技术实现三份文档中表述一致
|
||||
5. FreeRTOS 任务无死锁、无栈溢出、无优先级反转
|
||||
Reference in New Issue
Block a user