Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed1ece23c3 | |||
| 0e59416477 | |||
| 15c2f2776c | |||
| 322bed655c | |||
| e1f4767e9a | |||
| 9ce1eed850 | |||
| 004057a6fa | |||
| e203db13ca | |||
| d36f6b4bee | |||
| 2679db4129 | |||
| 245d98f58e |
+3
-2
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
1. `MUX`:全局数据承载模式开关
|
1. `MUX`:全局数据承载模式开关
|
||||||
2. `NET`:全局静态网络配置记录
|
2. `NET`:全局静态网络配置记录
|
||||||
3. `LINK[ROLE]`:按角色名组织的链路配置记录(`S1/S2/C1/C2`)
|
3. `LINK`:按角色名组织的链路配置记录(`S1/S2/C1/C2`)
|
||||||
|
|
||||||
约束如下:
|
约束如下:
|
||||||
|
|
||||||
@@ -452,4 +452,5 @@ AT+RESET\r\n
|
|||||||
|
|
||||||
- AT 命令实现:[config.c](/D:/code/STM32Project/TCP2UART/App/config.c)
|
- AT 命令实现:[config.c](/D:/code/STM32Project/TCP2UART/App/config.c)
|
||||||
- 配置结构与默认值:[config.h](/D:/code/STM32Project/TCP2UART/App/config.h)
|
- 配置结构与默认值:[config.h](/D:/code/STM32Project/TCP2UART/App/config.h)
|
||||||
- 调试与测试记录:[uart-ch390-debug-handoff.md](/D:/code/STM32Project/TCP2UART/uart-ch390-debug-handoff.md)
|
- 调试指导:[工程调试指南.md](/D:/code/STM32Project/TCP2UART/工程调试指南.md)
|
||||||
|
- 文档索引:[项目文档索引.md](/D:/code/STM32Project/TCP2UART/项目文档索引.md)
|
||||||
|
|||||||
+169
-15
@@ -17,7 +17,10 @@ typedef struct {
|
|||||||
uint8_t rx_ring[TCP_CLIENT_RX_BUFFER_SIZE];
|
uint8_t rx_ring[TCP_CLIENT_RX_BUFFER_SIZE];
|
||||||
uint16_t rx_head;
|
uint16_t rx_head;
|
||||||
uint16_t rx_tail;
|
uint16_t rx_tail;
|
||||||
|
struct pbuf *hold_pbuf;
|
||||||
|
uint16_t hold_offset;
|
||||||
uint32_t next_retry_ms;
|
uint32_t next_retry_ms;
|
||||||
|
uint32_t connect_start_ms;
|
||||||
uint8_t index;
|
uint8_t index;
|
||||||
tcp_client_instance_config_t config;
|
tcp_client_instance_config_t config;
|
||||||
tcp_client_status_t status;
|
tcp_client_status_t status;
|
||||||
@@ -30,10 +33,95 @@ static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
|||||||
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t ring_used(uint16_t head, uint16_t tail, uint16_t size)
|
||||||
|
{
|
||||||
|
return (head >= tail) ? (uint16_t)(head - tail) : (uint16_t)(size - tail + head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tick_reached(uint32_t now, uint32_t deadline)
|
||||||
|
{
|
||||||
|
return ((int32_t)(now - deadline) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_client_reset_rx_state(tcp_client_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx->hold_pbuf != NULL) {
|
||||||
|
pbuf_free(ctx->hold_pbuf);
|
||||||
|
ctx->hold_pbuf = NULL;
|
||||||
|
}
|
||||||
|
ctx->hold_offset = 0u;
|
||||||
|
ctx->rx_head = 0u;
|
||||||
|
ctx->rx_tail = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_client_abort_connect_timeout(tcp_client_ctx_t *ctx, uint32_t now)
|
||||||
|
{
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->pcb != NULL) {
|
||||||
|
tcp_arg(ctx->pcb, NULL);
|
||||||
|
tcp_recv(ctx->pcb, NULL);
|
||||||
|
tcp_sent(ctx->pcb, NULL);
|
||||||
|
tcp_err(ctx->pcb, NULL);
|
||||||
|
tcp_client_reset_rx_state(ctx);
|
||||||
|
tcp_abort(ctx->pcb);
|
||||||
|
ctx->pcb = NULL;
|
||||||
|
} else {
|
||||||
|
tcp_client_reset_rx_state(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
|
ctx->status.errors++;
|
||||||
|
ctx->status.connect_timeout_count++;
|
||||||
|
ctx->next_retry_ms = now + ctx->config.reconnect_interval_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_client_fill_ring_from_pbuf(tcp_client_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
struct pbuf *q;
|
||||||
|
uint16_t offset;
|
||||||
|
|
||||||
|
if (ctx == NULL || ctx->hold_pbuf == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
q = ctx->hold_pbuf;
|
||||||
|
offset = ctx->hold_offset;
|
||||||
|
while (q != NULL && offset >= q->len) {
|
||||||
|
offset = (uint16_t)(offset - q->len);
|
||||||
|
q = q->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (q != NULL) {
|
||||||
|
const uint8_t *src = (const uint8_t *)q->payload;
|
||||||
|
for (uint16_t i = offset; i < q->len; ++i) {
|
||||||
|
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_CLIENT_RX_BUFFER_SIZE) == 0u) {
|
||||||
|
ctx->hold_offset = (uint16_t)(ctx->hold_offset + i - offset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->rx_ring[ctx->rx_head] = src[i];
|
||||||
|
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
||||||
|
ctx->status.rx_bytes++;
|
||||||
|
}
|
||||||
|
ctx->hold_offset = (uint16_t)(ctx->hold_offset + q->len - offset);
|
||||||
|
offset = 0u;
|
||||||
|
q = q->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pbuf_free(ctx->hold_pbuf);
|
||||||
|
ctx->hold_pbuf = NULL;
|
||||||
|
ctx->hold_offset = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
static err_t tcp_client_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
static err_t tcp_client_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
||||||
{
|
{
|
||||||
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
|
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
|
||||||
struct pbuf *q;
|
|
||||||
|
|
||||||
if (ctx == NULL) {
|
if (ctx == NULL) {
|
||||||
if (p != NULL) {
|
if (p != NULL) {
|
||||||
@@ -54,26 +142,22 @@ static err_t tcp_client_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p,
|
|||||||
tcp_err(pcb, NULL);
|
tcp_err(pcb, NULL);
|
||||||
tcp_abort(pcb);
|
tcp_abort(pcb);
|
||||||
ctx->pcb = NULL;
|
ctx->pcb = NULL;
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||||
return ERR_ABRT;
|
return ERR_ABRT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (q = p; q != NULL; q = q->next) {
|
if (ctx->hold_pbuf != NULL) {
|
||||||
const uint8_t *src = (const uint8_t *)q->payload;
|
|
||||||
for (uint16_t i = 0; i < q->len; ++i) {
|
|
||||||
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_CLIENT_RX_BUFFER_SIZE) == 0u) {
|
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
break;
|
return ERR_MEM;
|
||||||
}
|
|
||||||
ctx->rx_ring[ctx->rx_head] = src[i];
|
|
||||||
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
|
||||||
ctx->status.rx_bytes++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp_recved(pcb, p->tot_len);
|
pbuf_ref(p);
|
||||||
|
ctx->hold_pbuf = p;
|
||||||
|
ctx->hold_offset = 0u;
|
||||||
pbuf_free(p);
|
pbuf_free(p);
|
||||||
|
tcp_client_fill_ring_from_pbuf(ctx);
|
||||||
return ERR_OK;
|
return ERR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +177,9 @@ static void tcp_client_on_err(void *arg, err_t err)
|
|||||||
if (ctx == NULL) {
|
if (ctx == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
tcp_client_reset_rx_state(ctx);
|
||||||
ctx->pcb = NULL;
|
ctx->pcb = NULL;
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||||
@@ -109,6 +195,7 @@ static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
|
|||||||
}
|
}
|
||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
ctx->pcb = NULL;
|
ctx->pcb = NULL;
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||||
@@ -116,6 +203,7 @@ static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx->pcb = pcb;
|
ctx->pcb = pcb;
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.state = TCP_CLIENT_STATE_CONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_CONNECTED;
|
||||||
tcp_nagle_disable(pcb);
|
tcp_nagle_disable(pcb);
|
||||||
tcp_arg(pcb, ctx);
|
tcp_arg(pcb, ctx);
|
||||||
@@ -175,6 +263,7 @@ int tcp_client_connect(uint8_t instance)
|
|||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
tcp_abort(pcb);
|
tcp_abort(pcb);
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -188,6 +277,7 @@ int tcp_client_connect(uint8_t instance)
|
|||||||
ctx->config.remote_ip[3]);
|
ctx->config.remote_ip[3]);
|
||||||
|
|
||||||
ctx->status.state = TCP_CLIENT_STATE_CONNECTING;
|
ctx->status.state = TCP_CLIENT_STATE_CONNECTING;
|
||||||
|
ctx->connect_start_ms = HAL_GetTick();
|
||||||
tcp_arg(pcb, ctx);
|
tcp_arg(pcb, ctx);
|
||||||
tcp_err(pcb, tcp_client_on_err);
|
tcp_err(pcb, tcp_client_on_err);
|
||||||
err = tcp_connect(pcb, &remote_addr, ctx->config.remote_port, tcp_client_on_connected);
|
err = tcp_connect(pcb, &remote_addr, ctx->config.remote_port, tcp_client_on_connected);
|
||||||
@@ -195,6 +285,7 @@ int tcp_client_connect(uint8_t instance)
|
|||||||
tcp_err(pcb, NULL);
|
tcp_err(pcb, NULL);
|
||||||
tcp_abort(pcb);
|
tcp_abort(pcb);
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -213,6 +304,7 @@ int tcp_client_disconnect(uint8_t instance)
|
|||||||
}
|
}
|
||||||
ctx = &g_clients[instance];
|
ctx = &g_clients[instance];
|
||||||
if (ctx->pcb != NULL) {
|
if (ctx->pcb != NULL) {
|
||||||
|
tcp_client_reset_rx_state(ctx);
|
||||||
tcp_arg(ctx->pcb, NULL);
|
tcp_arg(ctx->pcb, NULL);
|
||||||
tcp_recv(ctx->pcb, NULL);
|
tcp_recv(ctx->pcb, NULL);
|
||||||
tcp_sent(ctx->pcb, NULL);
|
tcp_sent(ctx->pcb, NULL);
|
||||||
@@ -220,9 +312,9 @@ int tcp_client_disconnect(uint8_t instance)
|
|||||||
tcp_abort(ctx->pcb);
|
tcp_abort(ctx->pcb);
|
||||||
ctx->pcb = NULL;
|
ctx->pcb = NULL;
|
||||||
}
|
}
|
||||||
|
ctx->connect_start_ms = 0u;
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
ctx->rx_head = 0u;
|
tcp_client_reset_rx_state(ctx);
|
||||||
ctx->rx_tail = 0u;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,13 +364,71 @@ int tcp_client_recv(uint8_t instance, uint8_t *data, uint16_t max_len)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
ctx = &g_clients[instance];
|
ctx = &g_clients[instance];
|
||||||
|
tcp_client_fill_ring_from_pbuf(ctx);
|
||||||
while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
|
while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
|
||||||
data[copied++] = ctx->rx_ring[ctx->rx_tail];
|
data[copied++] = ctx->rx_ring[ctx->rx_tail];
|
||||||
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
if (copied > 0u && ctx->pcb != NULL) {
|
||||||
|
tcp_recved(ctx->pcb, copied);
|
||||||
|
}
|
||||||
return (int)copied;
|
return (int)copied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t tcp_client_rx_available(uint8_t instance)
|
||||||
|
{
|
||||||
|
if (instance >= TCP_CLIENT_INSTANCE_COUNT) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
tcp_client_fill_ring_from_pbuf(&g_clients[instance]);
|
||||||
|
return ring_used(g_clients[instance].rx_head, g_clients[instance].rx_tail, TCP_CLIENT_RX_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tcp_client_peek(uint8_t instance, uint8_t *data, uint16_t max_len)
|
||||||
|
{
|
||||||
|
uint16_t copied = 0u;
|
||||||
|
uint16_t tail;
|
||||||
|
tcp_client_ctx_t *ctx;
|
||||||
|
|
||||||
|
if (instance >= TCP_CLIENT_INSTANCE_COUNT || data == NULL || max_len == 0u) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = &g_clients[instance];
|
||||||
|
tcp_client_fill_ring_from_pbuf(ctx);
|
||||||
|
tail = ctx->rx_tail;
|
||||||
|
while (copied < max_len && tail != ctx->rx_head) {
|
||||||
|
data[copied++] = ctx->rx_ring[tail];
|
||||||
|
tail = (uint16_t)((tail + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_client_drop(uint8_t instance, uint16_t len)
|
||||||
|
{
|
||||||
|
tcp_client_ctx_t *ctx;
|
||||||
|
uint16_t acked = 0u;
|
||||||
|
|
||||||
|
if (instance >= TCP_CLIENT_INSTANCE_COUNT || len == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = &g_clients[instance];
|
||||||
|
while (acked < len) {
|
||||||
|
tcp_client_fill_ring_from_pbuf(ctx);
|
||||||
|
if (ctx->rx_tail == ctx->rx_head) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while (acked < len && ctx->rx_tail != ctx->rx_head) {
|
||||||
|
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
||||||
|
acked++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (acked > 0u && ctx->pcb != NULL) {
|
||||||
|
tcp_recved(ctx->pcb, acked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool tcp_client_is_connected(uint8_t instance)
|
bool tcp_client_is_connected(uint8_t instance)
|
||||||
{
|
{
|
||||||
return (instance < TCP_CLIENT_INSTANCE_COUNT) &&
|
return (instance < TCP_CLIENT_INSTANCE_COUNT) &&
|
||||||
@@ -299,13 +449,17 @@ void tcp_client_poll(void)
|
|||||||
|
|
||||||
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
|
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
|
||||||
tcp_client_ctx_t *ctx = &g_clients[i];
|
tcp_client_ctx_t *ctx = &g_clients[i];
|
||||||
|
tcp_client_fill_ring_from_pbuf(ctx);
|
||||||
if (!ctx->config.enabled || !ctx->config.auto_reconnect || tcp_client_is_connected(i)) {
|
if (!ctx->config.enabled || !ctx->config.auto_reconnect || tcp_client_is_connected(i)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((ctx->pcb != NULL) && (ctx->status.state == TCP_CLIENT_STATE_CONNECTING)) {
|
if ((ctx->pcb != NULL) && (ctx->status.state == TCP_CLIENT_STATE_CONNECTING)) {
|
||||||
|
if ((uint32_t)(now - ctx->connect_start_ms) >= TCP_CLIENT_CONNECT_TIMEOUT_MS) {
|
||||||
|
tcp_client_abort_connect_timeout(ctx, now);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (now >= ctx->next_retry_ms) {
|
if (tick_reached(now, ctx->next_retry_ms)) {
|
||||||
ctx->status.reconnect_count++;
|
ctx->status.reconnect_count++;
|
||||||
ctx->next_retry_ms = now + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = now + ctx->config.reconnect_interval_ms;
|
||||||
(void)tcp_client_connect(i);
|
(void)tcp_client_connect(i);
|
||||||
|
|||||||
+6
-1
@@ -14,8 +14,9 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define TCP_CLIENT_INSTANCE_COUNT 2u
|
#define TCP_CLIENT_INSTANCE_COUNT 2u
|
||||||
#define TCP_CLIENT_RX_BUFFER_SIZE 512u
|
#define TCP_CLIENT_RX_BUFFER_SIZE 480u
|
||||||
#define TCP_CLIENT_RECONNECT_DELAY_MS 3000u
|
#define TCP_CLIENT_RECONNECT_DELAY_MS 3000u
|
||||||
|
#define TCP_CLIENT_CONNECT_TIMEOUT_MS 10000u
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
TCP_CLIENT_STATE_IDLE = 0,
|
TCP_CLIENT_STATE_IDLE = 0,
|
||||||
@@ -39,6 +40,7 @@ typedef struct {
|
|||||||
uint32_t rx_bytes;
|
uint32_t rx_bytes;
|
||||||
uint32_t tx_bytes;
|
uint32_t tx_bytes;
|
||||||
uint32_t reconnect_count;
|
uint32_t reconnect_count;
|
||||||
|
uint32_t connect_timeout_count;
|
||||||
uint32_t errors;
|
uint32_t errors;
|
||||||
} tcp_client_status_t;
|
} tcp_client_status_t;
|
||||||
|
|
||||||
@@ -48,6 +50,9 @@ int tcp_client_connect(uint8_t instance);
|
|||||||
int tcp_client_disconnect(uint8_t instance);
|
int tcp_client_disconnect(uint8_t instance);
|
||||||
int tcp_client_send(uint8_t instance, const uint8_t *data, uint16_t len);
|
int tcp_client_send(uint8_t instance, const uint8_t *data, uint16_t len);
|
||||||
int tcp_client_recv(uint8_t instance, uint8_t *data, uint16_t max_len);
|
int tcp_client_recv(uint8_t instance, uint8_t *data, uint16_t max_len);
|
||||||
|
uint16_t tcp_client_rx_available(uint8_t instance);
|
||||||
|
uint16_t tcp_client_peek(uint8_t instance, uint8_t *data, uint16_t max_len);
|
||||||
|
void tcp_client_drop(uint8_t instance, uint16_t len);
|
||||||
bool tcp_client_is_connected(uint8_t instance);
|
bool tcp_client_is_connected(uint8_t instance);
|
||||||
void tcp_client_get_status(uint8_t instance, tcp_client_status_t *status);
|
void tcp_client_get_status(uint8_t instance, tcp_client_status_t *status);
|
||||||
void tcp_client_poll(void);
|
void tcp_client_poll(void);
|
||||||
|
|||||||
+127
-14
@@ -18,6 +18,8 @@ typedef struct {
|
|||||||
uint8_t rx_ring[TCP_SERVER_RX_BUFFER_SIZE];
|
uint8_t rx_ring[TCP_SERVER_RX_BUFFER_SIZE];
|
||||||
uint16_t rx_head;
|
uint16_t rx_head;
|
||||||
uint16_t rx_tail;
|
uint16_t rx_tail;
|
||||||
|
struct pbuf *hold_pbuf;
|
||||||
|
uint16_t hold_offset;
|
||||||
uint8_t index;
|
uint8_t index;
|
||||||
tcp_server_instance_config_t config;
|
tcp_server_instance_config_t config;
|
||||||
tcp_server_status_t status;
|
tcp_server_status_t status;
|
||||||
@@ -30,10 +32,65 @@ static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
|||||||
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t ring_used(uint16_t head, uint16_t tail, uint16_t size)
|
||||||
|
{
|
||||||
|
return (head >= tail) ? (uint16_t)(head - tail) : (uint16_t)(size - tail + head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_server_reset_rx_state(tcp_server_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx->hold_pbuf != NULL) {
|
||||||
|
pbuf_free(ctx->hold_pbuf);
|
||||||
|
ctx->hold_pbuf = NULL;
|
||||||
|
}
|
||||||
|
ctx->hold_offset = 0u;
|
||||||
|
ctx->rx_head = 0u;
|
||||||
|
ctx->rx_tail = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_server_fill_ring_from_pbuf(tcp_server_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
struct pbuf *q;
|
||||||
|
uint16_t offset;
|
||||||
|
|
||||||
|
if (ctx == NULL || ctx->hold_pbuf == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
q = ctx->hold_pbuf;
|
||||||
|
offset = ctx->hold_offset;
|
||||||
|
while (q != NULL && offset >= q->len) {
|
||||||
|
offset = (uint16_t)(offset - q->len);
|
||||||
|
q = q->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (q != NULL) {
|
||||||
|
const uint8_t *src = (const uint8_t *)q->payload;
|
||||||
|
for (uint16_t i = offset; i < q->len; ++i) {
|
||||||
|
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_SERVER_RX_BUFFER_SIZE) == 0u) {
|
||||||
|
ctx->hold_offset = (uint16_t)(ctx->hold_offset + i - offset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->rx_ring[ctx->rx_head] = src[i];
|
||||||
|
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
||||||
|
ctx->status.rx_bytes++;
|
||||||
|
}
|
||||||
|
ctx->hold_offset = (uint16_t)(ctx->hold_offset + q->len - offset);
|
||||||
|
offset = 0u;
|
||||||
|
q = q->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pbuf_free(ctx->hold_pbuf);
|
||||||
|
ctx->hold_pbuf = NULL;
|
||||||
|
ctx->hold_offset = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
||||||
{
|
{
|
||||||
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
||||||
struct pbuf *q;
|
|
||||||
|
|
||||||
if (ctx == NULL) {
|
if (ctx == NULL) {
|
||||||
if (p != NULL) {
|
if (p != NULL) {
|
||||||
@@ -58,21 +115,16 @@ static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p,
|
|||||||
return ERR_ABRT;
|
return ERR_ABRT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (q = p; q != NULL; q = q->next) {
|
if (ctx->hold_pbuf != NULL) {
|
||||||
const uint8_t *src = (const uint8_t *)q->payload;
|
|
||||||
for (uint16_t i = 0; i < q->len; ++i) {
|
|
||||||
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_SERVER_RX_BUFFER_SIZE) == 0u) {
|
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
break;
|
return ERR_MEM;
|
||||||
}
|
|
||||||
ctx->rx_ring[ctx->rx_head] = src[i];
|
|
||||||
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
|
||||||
ctx->status.rx_bytes++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp_recved(pcb, p->tot_len);
|
pbuf_ref(p);
|
||||||
|
ctx->hold_pbuf = p;
|
||||||
|
ctx->hold_offset = 0u;
|
||||||
pbuf_free(p);
|
pbuf_free(p);
|
||||||
|
tcp_server_fill_ring_from_pbuf(ctx);
|
||||||
return ERR_OK;
|
return ERR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +144,7 @@ static void tcp_server_on_err(void *arg, err_t err)
|
|||||||
if (ctx == NULL) {
|
if (ctx == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
tcp_server_reset_rx_state(ctx);
|
||||||
ctx->client_pcb = NULL;
|
ctx->client_pcb = NULL;
|
||||||
ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE;
|
ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE;
|
||||||
ctx->status.errors++;
|
ctx->status.errors++;
|
||||||
@@ -193,6 +246,7 @@ int tcp_server_stop(uint8_t instance)
|
|||||||
ctx = &g_servers[instance];
|
ctx = &g_servers[instance];
|
||||||
|
|
||||||
if (ctx->client_pcb != NULL) {
|
if (ctx->client_pcb != NULL) {
|
||||||
|
tcp_server_reset_rx_state(ctx);
|
||||||
tcp_arg(ctx->client_pcb, NULL);
|
tcp_arg(ctx->client_pcb, NULL);
|
||||||
tcp_recv(ctx->client_pcb, NULL);
|
tcp_recv(ctx->client_pcb, NULL);
|
||||||
tcp_sent(ctx->client_pcb, NULL);
|
tcp_sent(ctx->client_pcb, NULL);
|
||||||
@@ -210,8 +264,7 @@ int tcp_server_stop(uint8_t instance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx->status.state = TCP_SERVER_STATE_IDLE;
|
ctx->status.state = TCP_SERVER_STATE_IDLE;
|
||||||
ctx->rx_head = 0u;
|
tcp_server_reset_rx_state(ctx);
|
||||||
ctx->rx_tail = 0u;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,13 +315,66 @@ int tcp_server_recv(uint8_t instance, uint8_t *data, uint16_t max_len)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
ctx = &g_servers[instance];
|
ctx = &g_servers[instance];
|
||||||
|
tcp_server_fill_ring_from_pbuf(ctx);
|
||||||
while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
|
while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
|
||||||
data[copied++] = ctx->rx_ring[ctx->rx_tail];
|
data[copied++] = ctx->rx_ring[ctx->rx_tail];
|
||||||
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
if (copied > 0u && ctx->client_pcb != NULL) {
|
||||||
|
tcp_recved(ctx->client_pcb, copied);
|
||||||
|
}
|
||||||
return (int)copied;
|
return (int)copied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t tcp_server_rx_available(uint8_t instance)
|
||||||
|
{
|
||||||
|
if (instance >= TCP_SERVER_INSTANCE_COUNT) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
tcp_server_fill_ring_from_pbuf(&g_servers[instance]);
|
||||||
|
return ring_used(g_servers[instance].rx_head, g_servers[instance].rx_tail, TCP_SERVER_RX_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tcp_server_peek(uint8_t instance, uint8_t *data, uint16_t max_len)
|
||||||
|
{
|
||||||
|
uint16_t copied = 0u;
|
||||||
|
uint16_t tail;
|
||||||
|
tcp_server_ctx_t *ctx;
|
||||||
|
|
||||||
|
if (instance >= TCP_SERVER_INSTANCE_COUNT || data == NULL || max_len == 0u) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = &g_servers[instance];
|
||||||
|
tcp_server_fill_ring_from_pbuf(ctx);
|
||||||
|
tail = ctx->rx_tail;
|
||||||
|
while (copied < max_len && tail != ctx->rx_head) {
|
||||||
|
data[copied++] = ctx->rx_ring[tail];
|
||||||
|
tail = (uint16_t)((tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_server_drop(uint8_t instance, uint16_t len)
|
||||||
|
{
|
||||||
|
tcp_server_ctx_t *ctx;
|
||||||
|
uint16_t dropped = 0u;
|
||||||
|
|
||||||
|
if (instance >= TCP_SERVER_INSTANCE_COUNT || len == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = &g_servers[instance];
|
||||||
|
while (dropped < len && ctx->rx_tail != ctx->rx_head) {
|
||||||
|
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
||||||
|
dropped++;
|
||||||
|
}
|
||||||
|
if (dropped > 0u && ctx->client_pcb != NULL) {
|
||||||
|
tcp_recved(ctx->client_pcb, dropped);
|
||||||
|
}
|
||||||
|
tcp_server_fill_ring_from_pbuf(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
bool tcp_server_is_connected(uint8_t instance)
|
bool tcp_server_is_connected(uint8_t instance)
|
||||||
{
|
{
|
||||||
return (instance < TCP_SERVER_INSTANCE_COUNT) && (g_servers[instance].client_pcb != NULL);
|
return (instance < TCP_SERVER_INSTANCE_COUNT) && (g_servers[instance].client_pcb != NULL);
|
||||||
@@ -280,3 +386,10 @@ void tcp_server_get_status(uint8_t instance, tcp_server_status_t *status)
|
|||||||
*status = g_servers[instance].status;
|
*status = g_servers[instance].status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tcp_server_poll(void)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
|
||||||
|
tcp_server_fill_ring_from_pbuf(&g_servers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+5
-1
@@ -14,7 +14,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define TCP_SERVER_INSTANCE_COUNT 2u
|
#define TCP_SERVER_INSTANCE_COUNT 2u
|
||||||
#define TCP_SERVER_RX_BUFFER_SIZE 512u
|
#define TCP_SERVER_RX_BUFFER_SIZE 480u
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
TCP_SERVER_STATE_IDLE = 0,
|
TCP_SERVER_STATE_IDLE = 0,
|
||||||
@@ -42,8 +42,12 @@ int tcp_server_start(uint8_t instance);
|
|||||||
int tcp_server_stop(uint8_t instance);
|
int tcp_server_stop(uint8_t instance);
|
||||||
int tcp_server_send(uint8_t instance, const uint8_t *data, uint16_t len);
|
int tcp_server_send(uint8_t instance, const uint8_t *data, uint16_t len);
|
||||||
int tcp_server_recv(uint8_t instance, uint8_t *data, uint16_t max_len);
|
int tcp_server_recv(uint8_t instance, uint8_t *data, uint16_t max_len);
|
||||||
|
uint16_t tcp_server_rx_available(uint8_t instance);
|
||||||
|
uint16_t tcp_server_peek(uint8_t instance, uint8_t *data, uint16_t max_len);
|
||||||
|
void tcp_server_drop(uint8_t instance, uint16_t len);
|
||||||
bool tcp_server_is_connected(uint8_t instance);
|
bool tcp_server_is_connected(uint8_t instance);
|
||||||
void tcp_server_get_status(uint8_t instance, tcp_server_status_t *status);
|
void tcp_server_get_status(uint8_t instance, tcp_server_status_t *status);
|
||||||
|
void tcp_server_poll(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,6 +281,14 @@ uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t
|
|||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t uart_trans_tx_free(uart_channel_t channel)
|
||||||
|
{
|
||||||
|
if (channel >= UART_CHANNEL_MAX) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
return ring_free(g_channels[channel].tx_head, g_channels[channel].tx_tail, UART_TX_RING_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats)
|
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats)
|
||||||
{
|
{
|
||||||
if (channel < UART_CHANNEL_MAX && stats != NULL) {
|
if (channel < UART_CHANNEL_MAX && stats != NULL) {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ void uart_trans_poll(void);
|
|||||||
uint16_t uart_trans_rx_available(uart_channel_t channel);
|
uint16_t uart_trans_rx_available(uart_channel_t channel);
|
||||||
uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len);
|
uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len);
|
||||||
uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t len);
|
uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t len);
|
||||||
|
uint16_t uart_trans_tx_free(uart_channel_t channel);
|
||||||
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats);
|
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats);
|
||||||
void uart_trans_reset_stats(uart_channel_t channel);
|
void uart_trans_reset_stats(uart_channel_t channel);
|
||||||
void uart_trans_idle_handler(uart_channel_t channel);
|
void uart_trans_idle_handler(uart_channel_t channel);
|
||||||
|
|||||||
+156
-15
@@ -38,6 +38,7 @@
|
|||||||
#define LED_PIN GPIO_PIN_13
|
#define LED_PIN GPIO_PIN_13
|
||||||
#define LED_PORT GPIOC
|
#define LED_PORT GPIOC
|
||||||
#define APP_ROUTE_BUFFER_SIZE 256u
|
#define APP_ROUTE_BUFFER_SIZE 256u
|
||||||
|
#define APP_TCP_TO_UART_CHUNK_SIZE 128u
|
||||||
#define STACK_GUARD_WORD 0xA5A5A5A5u
|
#define STACK_GUARD_WORD 0xA5A5A5A5u
|
||||||
#define APP_HEALTH_CHECK_INTERVAL_MS 5000u
|
#define APP_HEALTH_CHECK_INTERVAL_MS 5000u
|
||||||
/* USER CODE END PD */
|
/* USER CODE END PD */
|
||||||
@@ -67,6 +68,8 @@ static void App_RouteTcpTraffic(void);
|
|||||||
static void StackGuard_Init(void);
|
static void StackGuard_Init(void);
|
||||||
static void StackGuard_Check(void);
|
static void StackGuard_Check(void);
|
||||||
static bool App_SendToUart(uint8_t uart_index, uint8_t src_id, uint8_t dst_mask, const uint8_t *data, uint16_t len);
|
static bool App_SendToUart(uint8_t uart_index, uint8_t src_id, uint8_t dst_mask, const uint8_t *data, uint16_t len);
|
||||||
|
static uint16_t App_SendTcpPayloadToUartRaw(uint8_t uart_index, const uint8_t *data, uint16_t len);
|
||||||
|
static bool App_SendTcpPayloadToUartMux(uint8_t uart_index, uint8_t src_id, uint8_t dst_mask, const uint8_t *data, uint16_t len);
|
||||||
static bool App_SendTcpServerPayload(uint8_t instance, const uint8_t *data, uint16_t len);
|
static bool App_SendTcpServerPayload(uint8_t instance, const uint8_t *data, uint16_t len);
|
||||||
static bool App_SendTcpClientPayload(uint8_t instance, const uint8_t *data, uint16_t len);
|
static bool App_SendTcpClientPayload(uint8_t instance, const uint8_t *data, uint16_t len);
|
||||||
/* USER CODE END PFP */
|
/* USER CODE END PFP */
|
||||||
@@ -146,6 +149,19 @@ static void BootDiag_ReportCh390(void)
|
|||||||
cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3],
|
cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3],
|
||||||
cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3],
|
cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3],
|
||||||
cfg->mux_mode);
|
cfg->mux_mode);
|
||||||
|
SEGGER_RTT_printf(0,
|
||||||
|
"ETH rx ok=%u drop=%u pbuf_fail=%u filt=%u ipv6=%u udp=%u igmp=%u lldp=%u other_eth=%u other_ipv4=%u last=0x%04X\r\n",
|
||||||
|
(unsigned int)diag.rx_packets_ok,
|
||||||
|
(unsigned int)diag.rx_packets_drop,
|
||||||
|
(unsigned int)diag.rx_pbuf_alloc_failed,
|
||||||
|
(unsigned int)diag.rx_filtered_frames,
|
||||||
|
(unsigned int)diag.rx_filtered_ipv6,
|
||||||
|
(unsigned int)diag.rx_filtered_udp,
|
||||||
|
(unsigned int)diag.rx_filtered_igmp,
|
||||||
|
(unsigned int)diag.rx_filtered_lldp,
|
||||||
|
(unsigned int)diag.rx_filtered_other_eth,
|
||||||
|
(unsigned int)diag.rx_filtered_other_ipv4,
|
||||||
|
diag.last_eth_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void App_ConfigureLinks(const device_config_t *cfg)
|
static void App_ConfigureLinks(const device_config_t *cfg)
|
||||||
@@ -296,36 +312,160 @@ static bool App_SendToUart(uint8_t uart_index, uint8_t src_id, uint8_t dst_mask,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t App_SendTcpPayloadToUartRaw(uint8_t uart_index, const uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
|
||||||
|
return uart_trans_write(channel, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool App_SendTcpPayloadToUartMux(uint8_t uart_index, uint8_t src_id, uint8_t dst_mask, const uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
|
||||||
|
uint8_t frame[APP_TCP_TO_UART_CHUNK_SIZE + 6u];
|
||||||
|
uint16_t frame_len = 0u;
|
||||||
|
|
||||||
|
if (len == 0u || len > APP_TCP_TO_UART_CHUNK_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (uart_trans_tx_free(channel) < (uint16_t)(len + 6u)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!uart_mux_encode_frame(src_id, dst_mask, data, len, frame, &frame_len, sizeof(frame))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return uart_trans_write(channel, frame, frame_len) == frame_len;
|
||||||
|
}
|
||||||
|
|
||||||
static void App_RouteTcpTraffic(void)
|
static void App_RouteTcpTraffic(void)
|
||||||
{
|
{
|
||||||
const device_config_t *cfg = config_get();
|
const device_config_t *cfg = config_get();
|
||||||
uint8_t buffer[APP_ROUTE_BUFFER_SIZE];
|
uint8_t buffer[APP_TCP_TO_UART_CHUNK_SIZE];
|
||||||
|
|
||||||
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
|
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
|
||||||
int rc = tcp_server_recv(i, buffer, sizeof(buffer));
|
uint16_t available = tcp_server_rx_available(i);
|
||||||
if (rc > 0) {
|
if (available > 0u) {
|
||||||
uint8_t link_index = (i == 0u) ? CONFIG_LINK_S1 : CONFIG_LINK_S2;
|
uint8_t link_index = (i == 0u) ? CONFIG_LINK_S1 : CONFIG_LINK_S2;
|
||||||
if (!App_SendToUart(cfg->links[link_index].uart,
|
uint8_t uart_index = cfg->links[link_index].uart;
|
||||||
config_link_index_to_endpoint(link_index),
|
uint8_t src_id = config_link_index_to_endpoint(link_index);
|
||||||
config_uart_index_to_endpoint(cfg->links[link_index].uart),
|
uint8_t dst_mask = config_uart_index_to_endpoint(uart_index);
|
||||||
buffer,
|
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
|
||||||
(uint16_t)rc)) {
|
|
||||||
|
if (cfg->mux_mode == MUX_MODE_FRAME) {
|
||||||
|
uint16_t tx_free = uart_trans_tx_free(channel);
|
||||||
|
uint16_t payload_len;
|
||||||
|
if (tx_free <= 6u) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
payload_len = available;
|
||||||
|
if (payload_len > APP_TCP_TO_UART_CHUNK_SIZE) {
|
||||||
|
payload_len = APP_TCP_TO_UART_CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
if (payload_len > (uint16_t)(tx_free - 6u)) {
|
||||||
|
payload_len = (uint16_t)(tx_free - 6u);
|
||||||
|
}
|
||||||
|
if (payload_len == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
payload_len = tcp_server_peek(i, buffer, payload_len);
|
||||||
|
if (payload_len == 0u) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!App_SendTcpPayloadToUartMux(uart_index, src_id, dst_mask, buffer, payload_len)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tcp_server_drop(i, payload_len);
|
||||||
|
} else {
|
||||||
|
uint16_t chunk = available;
|
||||||
|
uint16_t tx_free = uart_trans_tx_free(channel);
|
||||||
|
uint16_t written;
|
||||||
|
if (tx_free == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chunk > APP_TCP_TO_UART_CHUNK_SIZE) {
|
||||||
|
chunk = APP_TCP_TO_UART_CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
if (chunk > tx_free) {
|
||||||
|
chunk = tx_free;
|
||||||
|
}
|
||||||
|
if (chunk == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunk = tcp_server_peek(i, buffer, chunk);
|
||||||
|
if (chunk == 0u) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
written = App_SendTcpPayloadToUartRaw(uart_index, buffer, chunk);
|
||||||
|
if (written > 0u) {
|
||||||
|
tcp_server_drop(i, written);
|
||||||
|
}
|
||||||
|
if (written < chunk) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
|
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
|
||||||
int rc = tcp_client_recv(i, buffer, sizeof(buffer));
|
uint16_t available = tcp_client_rx_available(i);
|
||||||
if (rc > 0) {
|
if (available > 0u) {
|
||||||
uint8_t link_index = (i == 0u) ? CONFIG_LINK_C1 : CONFIG_LINK_C2;
|
uint8_t link_index = (i == 0u) ? CONFIG_LINK_C1 : CONFIG_LINK_C2;
|
||||||
if (!App_SendToUart(cfg->links[link_index].uart,
|
uint8_t uart_index = cfg->links[link_index].uart;
|
||||||
config_link_index_to_endpoint(link_index),
|
uint8_t src_id = config_link_index_to_endpoint(link_index);
|
||||||
config_uart_index_to_endpoint(cfg->links[link_index].uart),
|
uint8_t dst_mask = config_uart_index_to_endpoint(uart_index);
|
||||||
buffer,
|
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
|
||||||
(uint16_t)rc)) {
|
|
||||||
|
if (cfg->mux_mode == MUX_MODE_FRAME) {
|
||||||
|
uint16_t tx_free = uart_trans_tx_free(channel);
|
||||||
|
uint16_t payload_len;
|
||||||
|
if (tx_free <= 6u) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
payload_len = available;
|
||||||
|
if (payload_len > APP_TCP_TO_UART_CHUNK_SIZE) {
|
||||||
|
payload_len = APP_TCP_TO_UART_CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
if (payload_len > (uint16_t)(tx_free - 6u)) {
|
||||||
|
payload_len = (uint16_t)(tx_free - 6u);
|
||||||
|
}
|
||||||
|
if (payload_len == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
payload_len = tcp_client_peek(i, buffer, payload_len);
|
||||||
|
if (payload_len == 0u) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!App_SendTcpPayloadToUartMux(uart_index, src_id, dst_mask, buffer, payload_len)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tcp_client_drop(i, payload_len);
|
||||||
|
} else {
|
||||||
|
uint16_t chunk = available;
|
||||||
|
uint16_t tx_free = uart_trans_tx_free(channel);
|
||||||
|
uint16_t written;
|
||||||
|
if (tx_free == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chunk > APP_TCP_TO_UART_CHUNK_SIZE) {
|
||||||
|
chunk = APP_TCP_TO_UART_CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
if (chunk > tx_free) {
|
||||||
|
chunk = tx_free;
|
||||||
|
}
|
||||||
|
if (chunk == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunk = tcp_client_peek(i, buffer, chunk);
|
||||||
|
if (chunk == 0u) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
written = App_SendTcpPayloadToUartRaw(uart_index, buffer, chunk);
|
||||||
|
if (written > 0u) {
|
||||||
|
tcp_client_drop(i, written);
|
||||||
|
}
|
||||||
|
if (written < chunk) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -510,6 +650,7 @@ static void App_Poll(void)
|
|||||||
sys_check_timeouts();
|
sys_check_timeouts();
|
||||||
App_StopLinksIfNeeded();
|
App_StopLinksIfNeeded();
|
||||||
App_StartLinksIfNeeded();
|
App_StartLinksIfNeeded();
|
||||||
|
tcp_server_poll();
|
||||||
tcp_client_poll();
|
tcp_client_poll();
|
||||||
uart_trans_poll();
|
uart_trans_poll();
|
||||||
StackGuard_Check();
|
StackGuard_Check();
|
||||||
|
|||||||
+38
-6
@@ -13,6 +13,7 @@
|
|||||||
#include "CH390_Interface.h"
|
#include "CH390_Interface.h"
|
||||||
|
|
||||||
#define CH390_PHY_BUSY_TIMEOUT_LOOPS 2000u
|
#define CH390_PHY_BUSY_TIMEOUT_LOOPS 2000u
|
||||||
|
#define CH390_RX_READ_PTR_HIGH 0x0Cu
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name ch390_receive_packet
|
* @name ch390_receive_packet
|
||||||
@@ -188,10 +189,36 @@ void ch390_write_eeprom(uint8_t reg, uint16_t value)
|
|||||||
void ch390_software_reset()
|
void ch390_software_reset()
|
||||||
{
|
{
|
||||||
ch390_write_reg(CH390_NCR, NCR_RST);
|
ch390_write_reg(CH390_NCR, NCR_RST);
|
||||||
ch390_delay_us(10);
|
for (uint8_t i = 0u; i < 20u; ++i) {
|
||||||
ch390_write_reg(CH390_NCR, 0);
|
ch390_delay_us(10u);
|
||||||
ch390_write_reg(CH390_NCR, NCR_RST);
|
if ((ch390_read_reg(CH390_NCR) & NCR_RST) == 0u) {
|
||||||
ch390_delay_us(10);
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch390_delay_us(1000u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ch390_reset_memory_pointers(void)
|
||||||
|
{
|
||||||
|
ch390_write_reg(CH390_MPTRCR, (uint8_t)(MPTRCR_RST_TX | MPTRCR_RST_RX));
|
||||||
|
for (uint8_t i = 0u; i < 20u; ++i) {
|
||||||
|
ch390_delay_us(1u);
|
||||||
|
if ((ch390_read_reg(CH390_MPTRCR) & (uint8_t)(MPTRCR_RST_TX | MPTRCR_RST_RX)) == 0u) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch390_write_reg(CH390_MRRL, 0x00u);
|
||||||
|
ch390_write_reg(CH390_MRRH, CH390_RX_READ_PTR_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ch390_phy_power_cycle(void)
|
||||||
|
{
|
||||||
|
ch390_write_reg(CH390_IMR, IMR_NONE);
|
||||||
|
ch390_write_reg(CH390_RCR, 0x00u);
|
||||||
|
ch390_write_reg(CH390_GPR, (uint8_t)(ch390_read_reg(CH390_GPR) | GPR_PHYPD));
|
||||||
|
ch390_delay_us(50000u);
|
||||||
|
ch390_write_reg(CH390_GPR, (uint8_t)(ch390_read_reg(CH390_GPR) & (uint8_t)(~GPR_PHYPD)));
|
||||||
|
ch390_delay_us(100000u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,6 +236,11 @@ void ch390_default_config()
|
|||||||
// Multicast address hash table
|
// Multicast address hash table
|
||||||
uint8_t multicase_addr[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
uint8_t multicase_addr[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
|
||||||
|
ch390_write_reg(CH390_IMR, IMR_NONE);
|
||||||
|
ch390_write_reg(CH390_RCR, 0x00u);
|
||||||
|
ch390_write_reg(CH390_ISR, 0xFFu);
|
||||||
|
ch390_reset_memory_pointers();
|
||||||
|
|
||||||
ch390_set_phy_mode(CH390_AUTO);
|
ch390_set_phy_mode(CH390_AUTO);
|
||||||
ch390_write_reg(CH390_INTCR, (uint8_t)(INCR_TYPE_OD | INCR_POL_L));
|
ch390_write_reg(CH390_INTCR, (uint8_t)(INCR_TYPE_OD | INCR_POL_L));
|
||||||
// Clear status
|
// Clear status
|
||||||
@@ -220,8 +252,8 @@ void ch390_default_config()
|
|||||||
// ch390_set_mac_address(mac_addr);
|
// ch390_set_mac_address(mac_addr);
|
||||||
ch390_set_multicast(multicase_addr);
|
ch390_set_multicast(multicase_addr);
|
||||||
|
|
||||||
// Enable only the interrupts needed by the NO_SYS polling path.
|
// Enable pointer auto-return and only the interrupts used by the NO_SYS polling path.
|
||||||
ch390_write_reg(CH390_IMR, (uint8_t)(IMR_PRI | IMR_LNKCHGI | IMR_ROOI | IMR_ROI));
|
ch390_write_reg(CH390_IMR, (uint8_t)(IMR_PAR | IMR_PRI | IMR_LNKCHGI | IMR_ROOI | IMR_ROI));
|
||||||
// Enable RX with the reference receive filter.
|
// Enable RX with the reference receive filter.
|
||||||
ch390_write_reg(CH390_RCR, RCR_DIS_CRC | RCR_RXEN);
|
ch390_write_reg(CH390_RCR, RCR_DIS_CRC | RCR_RXEN);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ enum ch390_phy_mode
|
|||||||
#define CH390_MAR 0x16
|
#define CH390_MAR 0x16
|
||||||
#define CH390_GPCR 0x1E
|
#define CH390_GPCR 0x1E
|
||||||
#define CH390_GPR 0x1F
|
#define CH390_GPR 0x1F
|
||||||
|
#define GPR_PHYPD (1<<0) // PHY power down
|
||||||
#define CH390_TRPAL 0x22
|
#define CH390_TRPAL 0x22
|
||||||
#define CH390_TRPAH 0x23
|
#define CH390_TRPAH 0x23
|
||||||
#define CH390_RWPAL 0x24
|
#define CH390_RWPAL 0x24
|
||||||
@@ -145,11 +146,14 @@ enum ch390_phy_mode
|
|||||||
#define INCR_POL_H 0x00
|
#define INCR_POL_H 0x00
|
||||||
#define CH390_ALNCR 0x4A
|
#define CH390_ALNCR 0x4A
|
||||||
#define CH390_SCCR 0x50
|
#define CH390_SCCR 0x50
|
||||||
|
#define SCCR_DIS_CLK (1<<0) // Stop internal clock
|
||||||
#define CH390_RSCCR 0x51
|
#define CH390_RSCCR 0x51
|
||||||
#define CH390_RLENCR 0x52
|
#define CH390_RLENCR 0x52
|
||||||
#define CH390_BCASTCR 0x53
|
#define CH390_BCASTCR 0x53
|
||||||
#define CH390_INTCKCR 0x54
|
#define CH390_INTCKCR 0x54
|
||||||
#define CH390_MPTRCR 0x55
|
#define CH390_MPTRCR 0x55
|
||||||
|
#define MPTRCR_RST_TX (1<<1) // Reset TX memory pointer
|
||||||
|
#define MPTRCR_RST_RX (1<<0) // Reset RX memory pointer
|
||||||
#define CH390_MLEDCR 0x57
|
#define CH390_MLEDCR 0x57
|
||||||
#define CH390_MRCMDX 0x70
|
#define CH390_MRCMDX 0x70
|
||||||
#define CH390_MRCMDX1 0x71
|
#define CH390_MRCMDX1 0x71
|
||||||
@@ -263,6 +267,7 @@ enum ch390_phy_mode
|
|||||||
#define CH390_MAR 0x16
|
#define CH390_MAR 0x16
|
||||||
#define CH390_GPCR 0x1E
|
#define CH390_GPCR 0x1E
|
||||||
#define CH390_GPR 0x1F
|
#define CH390_GPR 0x1F
|
||||||
|
#define GPR_PHYPD (1<<0) // PHY power down
|
||||||
#define CH390_TRPAL 0x22
|
#define CH390_TRPAL 0x22
|
||||||
#define CH390_TRPAH 0x23
|
#define CH390_TRPAH 0x23
|
||||||
#define CH390_RWPAL 0x24
|
#define CH390_RWPAL 0x24
|
||||||
@@ -298,10 +303,13 @@ enum ch390_phy_mode
|
|||||||
#define INCR_POL_L 0x01
|
#define INCR_POL_L 0x01
|
||||||
#define INCR_POL_H 0x00
|
#define INCR_POL_H 0x00
|
||||||
#define CH390_SCCR 0x50
|
#define CH390_SCCR 0x50
|
||||||
|
#define SCCR_DIS_CLK (1<<0) // Stop internal clock
|
||||||
#define CH390_RSCCR 0x51
|
#define CH390_RSCCR 0x51
|
||||||
#define CH390_RLENCR 0x52
|
#define CH390_RLENCR 0x52
|
||||||
#define CH390_BCASTCR 0x53
|
#define CH390_BCASTCR 0x53
|
||||||
#define CH390_MPTRCR 0x55
|
#define CH390_MPTRCR 0x55
|
||||||
|
#define MPTRCR_RST_TX (1<<1) // Reset TX memory pointer
|
||||||
|
#define MPTRCR_RST_RX (1<<0) // Reset RX memory pointer
|
||||||
#define CH390_MRCMDX 0xF0
|
#define CH390_MRCMDX 0xF0
|
||||||
#define CH390_MRCMDX1 0xF1
|
#define CH390_MRCMDX1 0xF1
|
||||||
#define CH390_MRCMD 0xF2
|
#define CH390_MRCMD 0xF2
|
||||||
@@ -424,6 +432,10 @@ void ch390_write_eeprom(uint8_t reg, uint16_t value);
|
|||||||
*/
|
*/
|
||||||
void ch390_software_reset(void);
|
void ch390_software_reset(void);
|
||||||
|
|
||||||
|
void ch390_reset_memory_pointers(void);
|
||||||
|
|
||||||
|
void ch390_phy_power_cycle(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name ch390_default_config
|
* @name ch390_default_config
|
||||||
* @brief Config CH390 with default options:
|
* @brief Config CH390 with default options:
|
||||||
|
|||||||
@@ -254,9 +254,9 @@ void ch390_hardware_reset(void)
|
|||||||
{
|
{
|
||||||
ch390_delay_us(10000); /* Short delay before reset */
|
ch390_delay_us(10000); /* Short delay before reset */
|
||||||
ch390_rst(0); /* Assert reset (low) */
|
ch390_rst(0); /* Assert reset (low) */
|
||||||
ch390_delay_us(3000); /* Hold reset for 3ms to satisfy datasheet minimum */
|
ch390_delay_us(50000); /* Recover chips stuck after repeated MCU resets */
|
||||||
ch390_rst(1); /* Release reset (high) */
|
ch390_rst(1); /* Release reset (high) */
|
||||||
ch390_delay_us(50000); /* Wait 50ms for CH390 to initialize reliably */
|
ch390_delay_us(500000); /* Allow EEPROM load, PHY, and SPI state to settle */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
|
|||||||
+238
-22
@@ -22,12 +22,19 @@ static uint8_t ch390_runtime_drain_rx(struct netif *netif, uint8_t max_frames)
|
|||||||
{
|
{
|
||||||
struct pbuf *p;
|
struct pbuf *p;
|
||||||
uint8_t drained = 0u;
|
uint8_t drained = 0u;
|
||||||
|
uint8_t rx_ready;
|
||||||
|
|
||||||
while (drained < max_frames) {
|
while (drained < max_frames) {
|
||||||
p = ch390_runtime_input_frame(netif);
|
p = ch390_runtime_input_frame(netif);
|
||||||
if (p == NULL) {
|
if (p == NULL) {
|
||||||
|
ch390_read_reg(CH390_MRCMDX);
|
||||||
|
rx_ready = ch390_read_reg(CH390_MRCMDX);
|
||||||
|
if ((rx_ready & CH390_PKT_RDY) == 0u) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
drained++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ch390_runtime_dispatch_frame(netif, p);
|
ch390_runtime_dispatch_frame(netif, p);
|
||||||
drained++;
|
drained++;
|
||||||
}
|
}
|
||||||
@@ -49,7 +56,179 @@ static uint8_t g_link_restart_pending;
|
|||||||
#define HEALTH_FAIL_SHIFT 4u
|
#define HEALTH_FAIL_SHIFT 4u
|
||||||
#define HEALTH_FAIL_MASK 0xF0u
|
#define HEALTH_FAIL_MASK 0xF0u
|
||||||
|
|
||||||
|
#define CH390_RX_FILTER_ENABLE 1u
|
||||||
|
#define CH390_RX_PREFIX_LEN 38u
|
||||||
|
#define CH390_ETH_HEADER_LEN 14u
|
||||||
|
#define CH390_IPV4_MIN_HEADER_LEN 20u
|
||||||
|
#define CH390_ETH_TYPE_IPV4 0x0800u
|
||||||
|
#define CH390_ETH_TYPE_ARP 0x0806u
|
||||||
|
#define CH390_ETH_TYPE_VLAN 0x8100u
|
||||||
|
#define CH390_ETH_TYPE_IPV6 0x86DDu
|
||||||
|
#define CH390_ETH_TYPE_QINQ 0x88A8u
|
||||||
|
#define CH390_ETH_TYPE_LLDP 0x88CCu
|
||||||
|
#define CH390_IP_PROTO_ICMP 1u
|
||||||
|
#define CH390_IP_PROTO_IGMP 2u
|
||||||
|
#define CH390_IP_PROTO_TCP 6u
|
||||||
|
#define CH390_IP_PROTO_UDP 17u
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CH390_RX_ACCEPT = 0,
|
||||||
|
CH390_RX_DROP_MALFORMED,
|
||||||
|
CH390_RX_DROP_IPV6,
|
||||||
|
CH390_RX_DROP_LLDP,
|
||||||
|
CH390_RX_DROP_UDP,
|
||||||
|
CH390_RX_DROP_IGMP,
|
||||||
|
CH390_RX_DROP_OTHER_ETH,
|
||||||
|
CH390_RX_DROP_OTHER_IPV4
|
||||||
|
} ch390_rx_filter_result_t;
|
||||||
|
|
||||||
static bool ch390_mac_address_valid(const uint8_t *mac);
|
static bool ch390_mac_address_valid(const uint8_t *mac);
|
||||||
|
static ch390_rx_filter_result_t ch390_runtime_filter_frame(const uint8_t *prefix, uint16_t frame_len, uint16_t prefix_len);
|
||||||
|
static void ch390_runtime_count_filtered_frame(ch390_rx_filter_result_t result);
|
||||||
|
static void ch390_runtime_copy_prefix_to_pbuf(struct pbuf *p, const uint8_t *prefix, uint16_t prefix_len);
|
||||||
|
static void ch390_runtime_read_remaining_to_pbuf(struct pbuf *p, uint16_t offset);
|
||||||
|
|
||||||
|
static ch390_rx_filter_result_t ch390_runtime_filter_frame(const uint8_t *prefix, uint16_t frame_len, uint16_t prefix_len)
|
||||||
|
{
|
||||||
|
uint16_t eth_type;
|
||||||
|
uint16_t l2_header_len = CH390_ETH_HEADER_LEN;
|
||||||
|
uint16_t ip_offset;
|
||||||
|
uint8_t ip_version;
|
||||||
|
uint8_t ip_ihl;
|
||||||
|
uint8_t ip_proto;
|
||||||
|
|
||||||
|
if ((prefix == NULL) || (frame_len < CH390_ETH_HEADER_LEN) || (prefix_len < CH390_ETH_HEADER_LEN)) {
|
||||||
|
return CH390_RX_DROP_MALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
eth_type = (uint16_t)(((uint16_t)prefix[12] << 8) | prefix[13]);
|
||||||
|
g_diag.last_eth_type = eth_type;
|
||||||
|
|
||||||
|
if ((eth_type == CH390_ETH_TYPE_VLAN) || (eth_type == CH390_ETH_TYPE_QINQ)) {
|
||||||
|
if ((frame_len < (CH390_ETH_HEADER_LEN + 4u)) || (prefix_len < (CH390_ETH_HEADER_LEN + 4u))) {
|
||||||
|
return CH390_RX_DROP_MALFORMED;
|
||||||
|
}
|
||||||
|
l2_header_len = (uint16_t)(CH390_ETH_HEADER_LEN + 4u);
|
||||||
|
eth_type = (uint16_t)(((uint16_t)prefix[16] << 8) | prefix[17]);
|
||||||
|
g_diag.last_eth_type = eth_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eth_type == CH390_ETH_TYPE_ARP) {
|
||||||
|
g_diag.rx_arp_frames++;
|
||||||
|
return CH390_RX_ACCEPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eth_type == CH390_ETH_TYPE_IPV6) {
|
||||||
|
return CH390_RX_DROP_IPV6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eth_type == CH390_ETH_TYPE_LLDP) {
|
||||||
|
return CH390_RX_DROP_LLDP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eth_type != CH390_ETH_TYPE_IPV4) {
|
||||||
|
return CH390_RX_DROP_OTHER_ETH;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_offset = l2_header_len;
|
||||||
|
if ((frame_len < (uint16_t)(ip_offset + CH390_IPV4_MIN_HEADER_LEN)) ||
|
||||||
|
(prefix_len < (uint16_t)(ip_offset + CH390_IPV4_MIN_HEADER_LEN))) {
|
||||||
|
return CH390_RX_DROP_MALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_version = (uint8_t)(prefix[ip_offset] >> 4);
|
||||||
|
ip_ihl = (uint8_t)(prefix[ip_offset] & 0x0Fu);
|
||||||
|
if ((ip_version != 4u) || (ip_ihl < 5u)) {
|
||||||
|
return CH390_RX_DROP_MALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_proto = prefix[ip_offset + 9u];
|
||||||
|
g_diag.rx_ip_frames++;
|
||||||
|
|
||||||
|
if (ip_proto == CH390_IP_PROTO_ICMP) {
|
||||||
|
g_diag.rx_ipv4_icmp_frames++;
|
||||||
|
return CH390_RX_ACCEPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip_proto == CH390_IP_PROTO_TCP) {
|
||||||
|
g_diag.rx_ipv4_tcp_frames++;
|
||||||
|
return CH390_RX_ACCEPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip_proto == CH390_IP_PROTO_UDP) {
|
||||||
|
g_diag.rx_ipv4_udp_frames++;
|
||||||
|
return CH390_RX_DROP_UDP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip_proto == CH390_IP_PROTO_IGMP) {
|
||||||
|
g_diag.rx_filtered_igmp++;
|
||||||
|
return CH390_RX_DROP_IGMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CH390_RX_DROP_OTHER_IPV4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ch390_runtime_count_filtered_frame(ch390_rx_filter_result_t result)
|
||||||
|
{
|
||||||
|
g_diag.rx_filtered_frames++;
|
||||||
|
switch (result) {
|
||||||
|
case CH390_RX_DROP_MALFORMED:
|
||||||
|
g_diag.rx_filtered_malformed++;
|
||||||
|
break;
|
||||||
|
case CH390_RX_DROP_IPV6:
|
||||||
|
g_diag.rx_filtered_ipv6++;
|
||||||
|
break;
|
||||||
|
case CH390_RX_DROP_LLDP:
|
||||||
|
g_diag.rx_filtered_lldp++;
|
||||||
|
break;
|
||||||
|
case CH390_RX_DROP_UDP:
|
||||||
|
g_diag.rx_filtered_udp++;
|
||||||
|
break;
|
||||||
|
case CH390_RX_DROP_IGMP:
|
||||||
|
break;
|
||||||
|
case CH390_RX_DROP_OTHER_ETH:
|
||||||
|
g_diag.rx_filtered_other_eth++;
|
||||||
|
break;
|
||||||
|
case CH390_RX_DROP_OTHER_IPV4:
|
||||||
|
g_diag.rx_filtered_other_ipv4++;
|
||||||
|
break;
|
||||||
|
case CH390_RX_ACCEPT:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ch390_runtime_copy_prefix_to_pbuf(struct pbuf *p, const uint8_t *prefix, uint16_t prefix_len)
|
||||||
|
{
|
||||||
|
struct pbuf *q;
|
||||||
|
uint16_t copied = 0u;
|
||||||
|
|
||||||
|
for (q = p; (q != NULL) && (copied < prefix_len); q = q->next) {
|
||||||
|
uint16_t chunk = (uint16_t)(prefix_len - copied);
|
||||||
|
if (chunk > q->len) {
|
||||||
|
chunk = q->len;
|
||||||
|
}
|
||||||
|
memcpy(q->payload, &prefix[copied], chunk);
|
||||||
|
copied = (uint16_t)(copied + chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ch390_runtime_read_remaining_to_pbuf(struct pbuf *p, uint16_t offset)
|
||||||
|
{
|
||||||
|
struct pbuf *q;
|
||||||
|
uint16_t skipped = 0u;
|
||||||
|
|
||||||
|
for (q = p; q != NULL; q = q->next) {
|
||||||
|
if (skipped >= offset) {
|
||||||
|
ch390_read_mem((uint8_t *)q->payload, q->len);
|
||||||
|
} else if ((uint16_t)(skipped + q->len) > offset) {
|
||||||
|
uint16_t in_chunk_offset = (uint16_t)(offset - skipped);
|
||||||
|
uint16_t read_len = (uint16_t)(q->len - in_chunk_offset);
|
||||||
|
ch390_read_mem(&((uint8_t *)q->payload)[in_chunk_offset], read_len);
|
||||||
|
}
|
||||||
|
skipped = (uint16_t)(skipped + q->len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t ch390_runtime_is_restart_pending(void)
|
static uint8_t ch390_runtime_is_restart_pending(void)
|
||||||
{
|
{
|
||||||
@@ -150,16 +329,49 @@ static void ch390_runtime_refresh_diag(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool ch390_runtime_recover_chip(struct netif *netif, const uint8_t *mac)
|
||||||
|
{
|
||||||
|
g_ch390_ready = 0u;
|
||||||
|
g_ch390_irq_pending = 0u;
|
||||||
|
|
||||||
|
ch390_hardware_reset();
|
||||||
|
if (ch390_runtime_probe_identity() == 0u) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ch390_software_reset();
|
||||||
|
if (ch390_runtime_probe_identity() == 0u) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ch390_phy_power_cycle();
|
||||||
|
ch390_default_config();
|
||||||
|
|
||||||
|
if (ch390_mac_address_valid(mac)) {
|
||||||
|
ch390_set_mac_address((uint8_t *)mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
ch390_runtime_prepare_netif(netif);
|
||||||
|
ch390_runtime_sync_mac(netif);
|
||||||
|
ch390_runtime_refresh_diag();
|
||||||
|
g_ch390_ready = g_diag.id_valid;
|
||||||
|
g_ch390_irq_pending = 0u;
|
||||||
|
|
||||||
|
return g_ch390_ready != 0u;
|
||||||
|
}
|
||||||
|
|
||||||
struct pbuf *ch390_runtime_input_frame(struct netif *netif)
|
struct pbuf *ch390_runtime_input_frame(struct netif *netif)
|
||||||
{
|
{
|
||||||
struct ethernetif *ethernetif = (struct ethernetif *)netif->state;
|
struct ethernetif *ethernetif = (struct ethernetif *)netif->state;
|
||||||
struct pbuf *p = NULL;
|
struct pbuf *p = NULL;
|
||||||
struct pbuf *q;
|
|
||||||
uint16_t len;
|
uint16_t len;
|
||||||
uint16_t frame_len;
|
uint16_t frame_len;
|
||||||
|
uint16_t prefix_len;
|
||||||
uint8_t rcr;
|
uint8_t rcr;
|
||||||
uint8_t rx_ready;
|
uint8_t rx_ready;
|
||||||
uint8_t rx_header[4];
|
uint8_t rx_header[4];
|
||||||
|
uint8_t frame_prefix[CH390_RX_PREFIX_LEN];
|
||||||
|
ch390_rx_filter_result_t filter_result;
|
||||||
ch390_read_reg(CH390_MRCMDX);
|
ch390_read_reg(CH390_MRCMDX);
|
||||||
rx_ready = ch390_read_reg(CH390_MRCMDX);
|
rx_ready = ch390_read_reg(CH390_MRCMDX);
|
||||||
|
|
||||||
@@ -196,6 +408,23 @@ struct pbuf *ch390_runtime_input_frame(struct netif *netif)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ethernetif->rx_len = frame_len;
|
ethernetif->rx_len = frame_len;
|
||||||
|
prefix_len = frame_len;
|
||||||
|
if (prefix_len > CH390_RX_PREFIX_LEN) {
|
||||||
|
prefix_len = CH390_RX_PREFIX_LEN;
|
||||||
|
}
|
||||||
|
ch390_read_mem(frame_prefix, prefix_len);
|
||||||
|
|
||||||
|
#if CH390_RX_FILTER_ENABLE
|
||||||
|
filter_result = ch390_runtime_filter_frame(frame_prefix, frame_len, prefix_len);
|
||||||
|
if (filter_result != CH390_RX_ACCEPT) {
|
||||||
|
ch390_drop_packet((uint16_t)(frame_len - prefix_len));
|
||||||
|
LINK_STATS_INC(link.drop);
|
||||||
|
g_diag.rx_packets_drop++;
|
||||||
|
ch390_runtime_count_filtered_frame(filter_result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
len = ethernetif->rx_len;
|
len = ethernetif->rx_len;
|
||||||
#if ETH_PAD_SIZE
|
#if ETH_PAD_SIZE
|
||||||
len += ETH_PAD_SIZE;
|
len += ETH_PAD_SIZE;
|
||||||
@@ -206,9 +435,8 @@ struct pbuf *ch390_runtime_input_frame(struct netif *netif)
|
|||||||
#if ETH_PAD_SIZE
|
#if ETH_PAD_SIZE
|
||||||
pbuf_remove_header(p, ETH_PAD_SIZE);
|
pbuf_remove_header(p, ETH_PAD_SIZE);
|
||||||
#endif
|
#endif
|
||||||
for (q = p; q != NULL; q = q->next) {
|
ch390_runtime_copy_prefix_to_pbuf(p, frame_prefix, prefix_len);
|
||||||
ch390_read_mem((uint8_t *)q->payload, q->len);
|
ch390_runtime_read_remaining_to_pbuf(p, prefix_len);
|
||||||
}
|
|
||||||
#if ETH_PAD_SIZE
|
#if ETH_PAD_SIZE
|
||||||
pbuf_add_header(p, ETH_PAD_SIZE);
|
pbuf_add_header(p, ETH_PAD_SIZE);
|
||||||
#endif
|
#endif
|
||||||
@@ -218,10 +446,11 @@ struct pbuf *ch390_runtime_input_frame(struct netif *netif)
|
|||||||
g_diag.last_frame_len = frame_len;
|
g_diag.last_frame_len = frame_len;
|
||||||
g_diag.last_payload_len = p->tot_len;
|
g_diag.last_payload_len = p->tot_len;
|
||||||
} else {
|
} else {
|
||||||
ch390_drop_packet(ethernetif->rx_len);
|
ch390_drop_packet((uint16_t)(frame_len - prefix_len));
|
||||||
LINK_STATS_INC(link.memerr);
|
LINK_STATS_INC(link.memerr);
|
||||||
LINK_STATS_INC(link.drop);
|
LINK_STATS_INC(link.drop);
|
||||||
g_diag.rx_packets_drop++;
|
g_diag.rx_packets_drop++;
|
||||||
|
g_diag.rx_pbuf_alloc_failed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
@@ -246,11 +475,8 @@ void ch390_runtime_init(struct netif *netif, const uint8_t *mac)
|
|||||||
ch390_gpio_init();
|
ch390_gpio_init();
|
||||||
SEGGER_RTT_WriteString(0, "ETH init: spi\r\n");
|
SEGGER_RTT_WriteString(0, "ETH init: spi\r\n");
|
||||||
ch390_spi_init();
|
ch390_spi_init();
|
||||||
SEGGER_RTT_WriteString(0, "ETH init: reset\r\n");
|
SEGGER_RTT_WriteString(0, "ETH init: deep reset\r\n");
|
||||||
ch390_hardware_reset();
|
g_ch390_ready = (uint8_t)ch390_runtime_recover_chip(netif, mac);
|
||||||
|
|
||||||
SEGGER_RTT_WriteString(0, "ETH init: probe\r\n");
|
|
||||||
g_ch390_ready = ch390_runtime_probe_identity();
|
|
||||||
if (g_ch390_ready == 0u) {
|
if (g_ch390_ready == 0u) {
|
||||||
ch390_runtime_prepare_netif(netif);
|
ch390_runtime_prepare_netif(netif);
|
||||||
netif_set_link_down(netif);
|
netif_set_link_down(netif);
|
||||||
@@ -258,11 +484,9 @@ void ch390_runtime_init(struct netif *netif, const uint8_t *mac)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SEGGER_RTT_WriteString(0, "ETH init: default\r\n");
|
|
||||||
ch390_default_config();
|
|
||||||
SEGGER_RTT_WriteString(0, "ETH init: mac\r\n");
|
SEGGER_RTT_WriteString(0, "ETH init: mac\r\n");
|
||||||
if (ch390_mac_address_valid(mac)) {
|
if (ch390_mac_address_valid(mac)) {
|
||||||
ch390_set_mac_address((uint8_t *)mac);
|
ch390_runtime_sync_mac(netif);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (mac != NULL) {
|
if (mac != NULL) {
|
||||||
@@ -462,15 +686,7 @@ bool ch390_runtime_emergency_reset(struct netif *netif)
|
|||||||
}
|
}
|
||||||
g_tx_consecutive_timeout = 0u;
|
g_tx_consecutive_timeout = 0u;
|
||||||
|
|
||||||
ch390_software_reset();
|
(void)ch390_runtime_recover_chip(netif, (netif != NULL) ? netif->hwaddr : NULL);
|
||||||
ch390_delay_us(5000u);
|
|
||||||
ch390_default_config();
|
|
||||||
ch390_runtime_prepare_netif(netif);
|
|
||||||
ch390_runtime_sync_mac(netif);
|
|
||||||
g_ch390_irq_pending = 0u;
|
|
||||||
|
|
||||||
ch390_runtime_refresh_diag();
|
|
||||||
g_ch390_ready = g_diag.id_valid;
|
|
||||||
|
|
||||||
if (g_ch390_ready == 0u) {
|
if (g_ch390_ready == 0u) {
|
||||||
SEGGER_RTT_WriteString(0, "ETH emergency reset: chip not responding\r\n");
|
SEGGER_RTT_WriteString(0, "ETH emergency reset: chip not responding\r\n");
|
||||||
|
|||||||
@@ -38,8 +38,20 @@ typedef struct {
|
|||||||
uint32_t rx_packets_drop;
|
uint32_t rx_packets_drop;
|
||||||
uint32_t tx_packets_ok;
|
uint32_t tx_packets_ok;
|
||||||
uint32_t tx_packets_timeout;
|
uint32_t tx_packets_timeout;
|
||||||
|
uint32_t rx_pbuf_alloc_failed;
|
||||||
|
uint32_t rx_filtered_frames;
|
||||||
|
uint32_t rx_filtered_ipv6;
|
||||||
|
uint32_t rx_filtered_udp;
|
||||||
|
uint32_t rx_filtered_igmp;
|
||||||
|
uint32_t rx_filtered_lldp;
|
||||||
|
uint32_t rx_filtered_other_eth;
|
||||||
|
uint32_t rx_filtered_other_ipv4;
|
||||||
|
uint32_t rx_filtered_malformed;
|
||||||
uint32_t rx_arp_frames;
|
uint32_t rx_arp_frames;
|
||||||
uint32_t rx_ip_frames;
|
uint32_t rx_ip_frames;
|
||||||
|
uint32_t rx_ipv4_icmp_frames;
|
||||||
|
uint32_t rx_ipv4_tcp_frames;
|
||||||
|
uint32_t rx_ipv4_udp_frames;
|
||||||
uint32_t rx_other_frames;
|
uint32_t rx_other_frames;
|
||||||
uint32_t rx_unicast_self_frames;
|
uint32_t rx_unicast_self_frames;
|
||||||
uint32_t rx_broadcast_frames;
|
uint32_t rx_broadcast_frames;
|
||||||
|
|||||||
-195
@@ -1,195 +0,0 @@
|
|||||||
========================================
|
|
||||||
TCP2UART Keil 工程配置说明
|
|
||||||
========================================
|
|
||||||
|
|
||||||
由于 Keil 工程文件格式复杂,建议在 Keil uVision 中手动添加以下配置。
|
|
||||||
|
|
||||||
========================================
|
|
||||||
一、添加包含路径 (Include Paths)
|
|
||||||
========================================
|
|
||||||
|
|
||||||
打开 Keil -> Project -> Options for Target -> C/C++ -> Include Paths
|
|
||||||
|
|
||||||
添加以下路径(用分号分隔):
|
|
||||||
../Drivers/CH390
|
|
||||||
../Drivers/LwIP/src/include
|
|
||||||
../Drivers/LwIP/src/include/lwip
|
|
||||||
../Drivers/LwIP/src/include/netif
|
|
||||||
../Drivers/LwIP/src/include/arch
|
|
||||||
../Drivers/LwIP/port
|
|
||||||
../App
|
|
||||||
|
|
||||||
完整的 Include Paths 应该是:
|
|
||||||
../Core/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3;../Drivers/CH390;../Drivers/LwIP/src/include;../Drivers/LwIP/src/include/lwip;../Drivers/LwIP/src/include/netif;../Drivers/LwIP/src/include/arch;../Drivers/LwIP/port;../App
|
|
||||||
|
|
||||||
========================================
|
|
||||||
二、添加源文件分组 (Source Groups)
|
|
||||||
========================================
|
|
||||||
|
|
||||||
在 Project 窗口中右键 -> Add Group,创建以下分组并添加文件:
|
|
||||||
|
|
||||||
【1】Drivers/CH390
|
|
||||||
添加文件:
|
|
||||||
- ../Drivers/CH390/CH390.c
|
|
||||||
- ../Drivers/CH390/CH390_Interface.c
|
|
||||||
|
|
||||||
【2】Drivers/LwIP/core
|
|
||||||
添加文件:
|
|
||||||
- ../Drivers/LwIP/src/core/def.c
|
|
||||||
- ../Drivers/LwIP/src/core/dns.c
|
|
||||||
- ../Drivers/LwIP/src/core/inet_chksum.c
|
|
||||||
- ../Drivers/LwIP/src/core/init.c
|
|
||||||
- ../Drivers/LwIP/src/core/ip.c
|
|
||||||
- ../Drivers/LwIP/src/core/mem.c
|
|
||||||
- ../Drivers/LwIP/src/core/memp.c
|
|
||||||
- ../Drivers/LwIP/src/core/netif.c
|
|
||||||
- ../Drivers/LwIP/src/core/pbuf.c
|
|
||||||
- ../Drivers/LwIP/src/core/raw.c
|
|
||||||
- ../Drivers/LwIP/src/core/stats.c
|
|
||||||
- ../Drivers/LwIP/src/core/sys.c
|
|
||||||
- ../Drivers/LwIP/src/core/tcp.c
|
|
||||||
- ../Drivers/LwIP/src/core/tcp_in.c
|
|
||||||
- ../Drivers/LwIP/src/core/tcp_out.c
|
|
||||||
- ../Drivers/LwIP/src/core/timeouts.c
|
|
||||||
- ../Drivers/LwIP/src/core/udp.c
|
|
||||||
|
|
||||||
IPv4 支持(在 core/ipv4 子目录):
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/autoip.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/dhcp.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/etharp.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/icmp.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/igmp.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/ip4.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/ip4_addr.c
|
|
||||||
- ../Drivers/LwIP/src/core/ipv4/ip4_frag.c
|
|
||||||
|
|
||||||
【3】Drivers/LwIP/api
|
|
||||||
添加文件:
|
|
||||||
- ../Drivers/LwIP/src/api/api_lib.c
|
|
||||||
- ../Drivers/LwIP/src/api/api_msg.c
|
|
||||||
- ../Drivers/LwIP/src/api/err.c
|
|
||||||
- ../Drivers/LwIP/src/api/netbuf.c
|
|
||||||
- ../Drivers/LwIP/src/api/netdb.c
|
|
||||||
- ../Drivers/LwIP/src/api/netifapi.c
|
|
||||||
- ../Drivers/LwIP/src/api/sockets.c
|
|
||||||
- ../Drivers/LwIP/src/api/tcpip.c
|
|
||||||
|
|
||||||
【4】Drivers/LwIP/netif
|
|
||||||
添加文件:
|
|
||||||
- ../Drivers/LwIP/src/netif/ethernetif.c
|
|
||||||
|
|
||||||
【5】Drivers/LwIP/port
|
|
||||||
添加文件:
|
|
||||||
- ../Drivers/LwIP/port/sys_arch.c
|
|
||||||
|
|
||||||
【6】App
|
|
||||||
添加文件:
|
|
||||||
- ../App/tcp_server.c
|
|
||||||
- ../App/tcp_client.c
|
|
||||||
- ../App/uart_trans.c
|
|
||||||
- ../App/config.c
|
|
||||||
- ../App/flash_param.c
|
|
||||||
|
|
||||||
========================================
|
|
||||||
三、预处理器宏定义 (Preprocessor Defines)
|
|
||||||
========================================
|
|
||||||
|
|
||||||
打开 Keil -> Project -> Options for Target -> C/C++ -> Define
|
|
||||||
|
|
||||||
保持现有定义,不需要额外添加:
|
|
||||||
USE_HAL_DRIVER,STM32F103xB
|
|
||||||
|
|
||||||
========================================
|
|
||||||
四、编译优化设置
|
|
||||||
========================================
|
|
||||||
|
|
||||||
建议设置:
|
|
||||||
- Optimization: Level 2 (-O2)
|
|
||||||
- 勾选 "One ELF Section per Function"
|
|
||||||
- Warning Level: All Warnings
|
|
||||||
|
|
||||||
========================================
|
|
||||||
五、目标内存配置
|
|
||||||
========================================
|
|
||||||
|
|
||||||
确认 ROM 和 RAM 配置正确:
|
|
||||||
- IROM1: 0x08000000, Size: 0x10000 (64KB)
|
|
||||||
- IRAM1: 0x20000000, Size: 0x5000 (20KB)
|
|
||||||
|
|
||||||
========================================
|
|
||||||
六、编译验证
|
|
||||||
========================================
|
|
||||||
|
|
||||||
配置完成后:
|
|
||||||
1. 按 F7 编译整个工程
|
|
||||||
2. 检查是否有编译错误
|
|
||||||
3. 常见问题:
|
|
||||||
- "file not found" -> 检查包含路径
|
|
||||||
- "undefined reference" -> 检查是否添加了所有源文件
|
|
||||||
- 链接错误 -> 检查 ROM/RAM 大小配置
|
|
||||||
|
|
||||||
========================================
|
|
||||||
七、烧录配置
|
|
||||||
========================================
|
|
||||||
|
|
||||||
Debug 选项卡:
|
|
||||||
- 选择正确的调试器(ST-Link/J-Link)
|
|
||||||
- 勾选 "Reset and Run"
|
|
||||||
|
|
||||||
Utilities 选项卡:
|
|
||||||
- 选择正确的 Flash 算法
|
|
||||||
- STM32F10x Med-density Flash (64KB)
|
|
||||||
|
|
||||||
========================================
|
|
||||||
快速添加方法(可选)
|
|
||||||
========================================
|
|
||||||
|
|
||||||
如果源文件太多手动添加麻烦,可以:
|
|
||||||
|
|
||||||
1. 在 Keil 中右键分组 -> Add Existing Files
|
|
||||||
2. 选择 "All Files (*.*)"
|
|
||||||
3. 导航到对应目录
|
|
||||||
4. 按住 Ctrl 多选所有 .c 文件
|
|
||||||
5. 点击 Add
|
|
||||||
|
|
||||||
========================================
|
|
||||||
文件结构参考
|
|
||||||
========================================
|
|
||||||
|
|
||||||
TCP2UART/
|
|
||||||
├── App/
|
|
||||||
│ ├── tcp_server.c/h
|
|
||||||
│ ├── tcp_client.c/h
|
|
||||||
│ ├── uart_trans.c/h
|
|
||||||
│ ├── config.c/h
|
|
||||||
│ └── flash_param.c/h
|
|
||||||
├── Core/
|
|
||||||
│ ├── Inc/
|
|
||||||
│ └── Src/
|
|
||||||
├── Drivers/
|
|
||||||
│ ├── CH390/
|
|
||||||
│ │ ├── CH390.c/h
|
|
||||||
│ │ └── CH390_Interface.c/h
|
|
||||||
│ ├── LwIP/
|
|
||||||
│ │ ├── port/
|
|
||||||
│ │ │ └── sys_arch.c
|
|
||||||
│ │ └── src/
|
|
||||||
│ │ ├── api/
|
|
||||||
│ │ ├── core/
|
|
||||||
│ │ │ └── ipv4/
|
|
||||||
│ │ ├── include/
|
|
||||||
│ │ │ ├── arch/
|
|
||||||
│ │ │ │ ├── cc.h
|
|
||||||
│ │ │ │ ├── lwipopts.h
|
|
||||||
│ │ │ │ └── sys_arch.h
|
|
||||||
│ │ │ ├── lwip/
|
|
||||||
│ │ │ └── netif/
|
|
||||||
│ │ └── netif/
|
|
||||||
│ │ └── ethernetif.c/h
|
|
||||||
│ └── STM32F1xx_HAL_Driver/
|
|
||||||
├── MDK-ARM/
|
|
||||||
│ └── TCP2UART.uvprojx
|
|
||||||
└── Middlewares/
|
|
||||||
└── Third_Party/FreeRTOS/
|
|
||||||
|
|
||||||
========================================
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
|
|
||||||
632 0 0 0 0 0 ch390.o
|
|
||||||
616 0 64 0 0 0 ch390_interface.o
|
|
||||||
2050 0 85 6 88 0 ch390_runtime.o
|
|
||||||
3958 0 591 8 1240 0 config.o
|
|
||||||
8 0 0 0 0 0 def.o
|
|
||||||
124 0 0 0 0 0 dma.o
|
|
||||||
1772 0 0 1 240 0 etharp.o
|
|
||||||
238 0 12 0 0 0 ethernet.o
|
|
||||||
178 0 0 0 48 0 ethernetif.o
|
|
||||||
246 0 0 0 0 0 flash_param.o
|
|
||||||
240 0 0 0 0 0 gpio.o
|
|
||||||
452 0 0 0 0 0 icmp.o
|
|
||||||
334 0 0 0 0 0 inet_chksum.o
|
|
||||||
26 0 0 0 0 0 init.o
|
|
||||||
0 0 0 0 24 0 ip.o
|
|
||||||
778 0 0 2 0 0 ip4.o
|
|
||||||
46 0 4 0 0 0 ip4_addr.o
|
|
||||||
44 0 0 0 12 0 iwdg.o
|
|
||||||
2842 0 185 12 272 0 main.o
|
|
||||||
828 0 0 12 4115 0 mem.o
|
|
||||||
196 0 244 32 6464 0 memp.o
|
|
||||||
582 0 0 12 0 0 netif.o
|
|
||||||
1118 0 0 0 0 0 pbuf.o
|
|
||||||
248 0 0 4 0 0 raw.o
|
|
||||||
214 0 9 168 272 0 segger_rtt.o
|
|
||||||
64 0 0 0 0 0 segger_rtt_printf.o
|
|
||||||
216 0 0 0 88 0 spi.o
|
|
||||||
60 0 236 0 1024 0 startup_stm32f103xb.o
|
|
||||||
128 0 0 12 0 0 stm32f1xx_hal.o
|
|
||||||
198 0 0 0 0 0 stm32f1xx_hal_cortex.o
|
|
||||||
808 0 0 0 0 0 stm32f1xx_hal_dma.o
|
|
||||||
392 0 0 0 32 0 stm32f1xx_hal_flash.o
|
|
||||||
240 0 0 0 0 0 stm32f1xx_hal_flash_ex.o
|
|
||||||
516 0 0 0 0 0 stm32f1xx_hal_gpio.o
|
|
||||||
106 0 0 0 0 0 stm32f1xx_hal_iwdg.o
|
|
||||||
60 0 0 0 0 0 stm32f1xx_hal_msp.o
|
|
||||||
1240 0 18 0 0 0 stm32f1xx_hal_rcc.o
|
|
||||||
1510 0 0 0 0 0 stm32f1xx_hal_spi.o
|
|
||||||
936 0 0 0 0 0 stm32f1xx_hal_tim.o
|
|
||||||
108 0 0 0 0 0 stm32f1xx_hal_tim_ex.o
|
|
||||||
2300 0 0 0 0 0 stm32f1xx_hal_uart.o
|
|
||||||
490 0 0 0 0 0 stm32f1xx_it.o
|
|
||||||
2 0 24 4 0 0 system_stm32f1xx.o
|
|
||||||
3474 0 193 32 0 0 tcp.o
|
|
||||||
1232 0 0 0 1120 0 tcp_client.o
|
|
||||||
3684 0 0 36 20 0 tcp_in.o
|
|
||||||
3862 0 0 0 0 0 tcp_out.o
|
|
||||||
986 0 0 0 1104 0 tcp_server.o
|
|
||||||
164 0 0 0 72 0 tim.o
|
|
||||||
374 0 16 12 0 0 timeouts.o
|
|
||||||
1538 0 0 0 2936 0 uart_trans.o
|
|
||||||
816 0 0 0 624 0 usart.o
|
|
||||||
Object Totals
|
|
||||||
|
|
||||||
Memory Map of the image
|
|
||||||
|
|
||||||
Load Region LR_IROM1
|
|
||||||
|
|
||||||
Execution Region ER_IROM1 (Exec base: 0x08000000, Size: 0x0000D72C, Max: 0x00010000, END)
|
|
||||||
|
|
||||||
Execution Region RW_IRAM1 (Exec base: 0x20000000, Size: 0x00005000, Max: 0x00005000, END)
|
|
||||||
|
|
||||||
Image component sizes
|
|
||||||
@@ -1,586 +0,0 @@
|
|||||||
# UART CH390 Debug Handoff
|
|
||||||
|
|
||||||
## 2026-03-31 Config UART Test Session
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
- Exhaustively test the `USART1` config command interface.
|
|
||||||
- Verify that Flash-backed parameters survive `AT+SAVE` plus reset.
|
|
||||||
- Record concrete bench procedure, failures, fixes, and evidence paths.
|
|
||||||
|
|
||||||
### Bench Baseline
|
|
||||||
|
|
||||||
- Workspace: `D:\code\STM32Project\TCP2UART`
|
|
||||||
- Target MCU: `STM32F103R8T6`
|
|
||||||
- Config UART: `USART1` on `PA9/PA10`
|
|
||||||
- Host visible COM ports during this session: `COM1`, `COM9`
|
|
||||||
- Debug probe visible during this session: `STLink V2`
|
|
||||||
- Flash parameter page: `0x0800FC00`
|
|
||||||
- Firmware image used for test bring-up: `MDK-ARM\TCP2UART\TCP2UART.axf`
|
|
||||||
|
|
||||||
### Source-of-Truth Command Surface
|
|
||||||
|
|
||||||
From `App/config.c`, the tested command surface is:
|
|
||||||
|
|
||||||
- `AT`
|
|
||||||
- `AT+?`
|
|
||||||
- `AT+QUERY`
|
|
||||||
- `AT+SAVE`
|
|
||||||
- `AT+RESET`
|
|
||||||
- `AT+DEFAULT`
|
|
||||||
- `AT+IP=...`
|
|
||||||
- `AT+MASK=...`
|
|
||||||
- `AT+GW=...`
|
|
||||||
- `AT+RIP=...`
|
|
||||||
- `AT+MAC=...`
|
|
||||||
- `AT+PORT=...`
|
|
||||||
- `AT+RPORT=...`
|
|
||||||
- `AT+BAUD1=...`
|
|
||||||
- `AT+BAUD2=...`
|
|
||||||
- `AT+DHCP=0/1`
|
|
||||||
|
|
||||||
### Test Procedure
|
|
||||||
|
|
||||||
1. Flash the current `axf` image with `probe-rs download --chip STM32F103R8`.
|
|
||||||
2. Connect to the config UART at `115200 8N1`.
|
|
||||||
3. Run `python tools/uart_config_test.py --port COM9 --scenario inventory`.
|
|
||||||
4. Run `python tools/uart_config_test.py --port COM9 --scenario persistence`.
|
|
||||||
5. Read back Flash words at `0x0800FC00` and compare them before and after reset.
|
|
||||||
6. Save all transcripts into `artifacts/uart-config/`.
|
|
||||||
|
|
||||||
### Important Expectations
|
|
||||||
|
|
||||||
- Setter commands update RAM immediately and return `OK` plus the reboot hint.
|
|
||||||
- Persistence is not proven until the sequence `set -> query -> save -> reset -> query` passes.
|
|
||||||
- `AT+DEFAULT` only resets RAM state and still requires `AT+SAVE` to persist.
|
|
||||||
- `AT+DHCP=1` must fail in this build by design.
|
|
||||||
|
|
||||||
### Session Findings
|
|
||||||
|
|
||||||
- `probe-rs download` succeeded against `STM32F103R8`.
|
|
||||||
- `pyserial` had to be installed on the host before scripted UART testing.
|
|
||||||
- The requested handoff filename did not exist in the repo, so this file was created as the dedicated config-UART handoff log.
|
|
||||||
- Raw command transcripts and Flash comparisons should be attached from `artifacts/uart-config/` for any future regression analysis.
|
|
||||||
|
|
||||||
### 2026-03-31 Live Test Evidence
|
|
||||||
|
|
||||||
- Host-side strict inventory run on `COM9` failed with `non_empty_responses = 0`.
|
|
||||||
- Artifact: `artifacts/uart-config/inventory-20260331-185333.json`
|
|
||||||
- Direct host probe on `COM9` with `AT\r\n` returned `b''`.
|
|
||||||
- Direct host probe on `COM1` with `AT\r\n` also returned `b''`.
|
|
||||||
- Strict persistence run was intentionally not accepted as valid after the script was corrected, because all command responses were empty.
|
|
||||||
- Flash page `0x0800FC00` remained all `0xFFFFFFFF`, proving `AT+SAVE` never actually executed on target.
|
|
||||||
|
|
||||||
### Board/Debugger Findings That Narrow The Fault
|
|
||||||
|
|
||||||
- The target firmware is present in Flash and `probe-rs download` completes successfully.
|
|
||||||
- After a clean reset and short run window, the MCU executes from Flash and initializes clocks and `USART1` registers.
|
|
||||||
- `USART1` register block was observed in an initialized state after boot, not all-zero.
|
|
||||||
- Firmware was patched to explicitly start `HAL_UART_Receive_IT(&huart1, &g_uart1_rx_probe_byte, 1)` during `App_Init()`.
|
|
||||||
- Even after that fix, repeated `AT` frames from the host produced no visible UART response.
|
|
||||||
- Debug readout of software-side config RX state showed:
|
|
||||||
- `g_pending_cmd_ready = 0`
|
|
||||||
- `g_pending_cmd_len = 0`
|
|
||||||
- `g_uart_cmd_len = 0`
|
|
||||||
- `g_uart_cmd_buffer` remained zero-filled
|
|
||||||
- `g_pending_cmd_buffer` remained zero-filled
|
|
||||||
- This means the config parser never received any bytes from the live UART path during the test window.
|
|
||||||
|
|
||||||
### Current Best Conclusion
|
|
||||||
|
|
||||||
- The immediate blocker is no longer the parser itself.
|
|
||||||
- Current evidence points to a board-level `USART1_RX` path problem or wrong host wiring/port assumption, because the firmware is alive, `USART1` is initialized, but no command bytes enter `config_uart_rx_byte()`.
|
|
||||||
- Until the physical config-UART path is proven, it is not meaningful to claim that every config command or Flash persistence path has passed on real hardware.
|
|
||||||
|
|
||||||
### Corrected Final Conclusion
|
|
||||||
|
|
||||||
- The earlier "no response" conclusion was wrong because the host was sending `\r\n` terminated commands.
|
|
||||||
- This firmware expects the config command to be terminated by `\n` to complete the frame in the real bench setup.
|
|
||||||
- After switching the host sender to `AT...\n`, `COM9` immediately returned `OK\r\n` for `AT` and the complete config command set became testable.
|
|
||||||
- Therefore the key bench rule is: every config command must end with `\n`.
|
|
||||||
|
|
||||||
### Final Verified Results
|
|
||||||
|
|
||||||
- `AT` returns `OK`.
|
|
||||||
- `AT+?` and `AT+QUERY` return the full current configuration snapshot.
|
|
||||||
- Setter commands `AT+IP`, `AT+MASK`, `AT+GW`, `AT+RIP`, `AT+MAC`, `AT+PORT`, `AT+RPORT`, `AT+BAUD1`, `AT+BAUD2`, `AT+DHCP=0` all return `OK` plus the reboot hint.
|
|
||||||
- `AT+SAVE` returns `OK: Configuration saved`.
|
|
||||||
- `AT+RESET` returns `OK: Resetting...` and the board comes back responding to `AT`.
|
|
||||||
- `AT+DEFAULT` returns `OK: Defaults restored`.
|
|
||||||
- Negative cases were verified:
|
|
||||||
- `AT+UNKNOWN` -> `ERROR: Unknown command`
|
|
||||||
- `AT+PORT=0` / `AT+PORT=65536` -> `ERROR: Invalid port`
|
|
||||||
- `AT+BAUD1=1199` / `AT+BAUD1=921601` -> `ERROR: Invalid baudrate`
|
|
||||||
- `AT+DHCP=1` -> `ERROR: DHCP disabled in this build`
|
|
||||||
- `AT+IP=999.1.1.1` -> `ERROR: Invalid IP format`
|
|
||||||
- `AT+MAC=GG:11:22:33:44:55` -> `ERROR: Invalid MAC format`
|
|
||||||
- Non-AT input `BT` produced no response, which matches the parser gate.
|
|
||||||
|
|
||||||
### Final Flash Persistence Evidence
|
|
||||||
|
|
||||||
- Tested persisted values:
|
|
||||||
- `IP=192.168.1.123`
|
|
||||||
- `MASK=255.255.255.0`
|
|
||||||
- `GW=192.168.1.1`
|
|
||||||
- `RIP=192.168.1.201`
|
|
||||||
- `MAC=02:12:34:56:78:9A`
|
|
||||||
- `PORT=10001`
|
|
||||||
- `RPORT=10002`
|
|
||||||
- `BAUD1=57600`
|
|
||||||
- `BAUD2=38400`
|
|
||||||
- Sequence used:
|
|
||||||
1. set values with `\n`-terminated AT commands
|
|
||||||
2. query with `AT+?`
|
|
||||||
3. `AT+SAVE`
|
|
||||||
4. `AT+RESET`
|
|
||||||
5. query again with `AT+?`
|
|
||||||
6. read raw Flash words at `0x0800FC00`
|
|
||||||
- Query values before and after reset matched exactly.
|
|
||||||
- Raw Flash read before and after reset also matched exactly.
|
|
||||||
- Factory default restoration was also proven with `AT+DEFAULT -> AT+SAVE -> AT+RESET -> AT+?`.
|
|
||||||
|
|
||||||
### Evidence Files
|
|
||||||
|
|
||||||
- Inventory transcript: `artifacts/uart-config/inventory-20260331-185752.json`
|
|
||||||
- Inventory raw text: `artifacts/uart-config/inventory-20260331-185752.txt`
|
|
||||||
- Persistence transcript: `artifacts/uart-config/persistence-20260331-190039.json`
|
|
||||||
- Persistence raw text: `artifacts/uart-config/persistence-20260331-190039.txt`
|
|
||||||
|
|
||||||
### Firmware Adjustment Made During This Session
|
|
||||||
|
|
||||||
- Added explicit `HAL_UART_Receive_IT(&huart1, &g_uart1_rx_probe_byte, 1u)` arming in `App_Init()` so the `USART1` interrupt receive path is definitely started after boot.
|
|
||||||
- This is a safe, minimal bring-up fix and should remain in place.
|
|
||||||
|
|
||||||
### Practical Lessons
|
|
||||||
|
|
||||||
- Do not treat a script exit code alone as proof of UART success; require at least one non-empty response in the captured transcript.
|
|
||||||
- Do not treat `probe-rs read 0x0800FC00` returning all `0xFFFFFFFF` as a flash-driver failure until you first prove that `AT+SAVE` was actually accepted by the parser.
|
|
||||||
- In this project, the fastest truth test is:
|
|
||||||
1. prove target is executing
|
|
||||||
2. prove `USART1` is initialized
|
|
||||||
3. prove bytes reach `config_uart_rx_byte`
|
|
||||||
4. only then evaluate parser responses and flash persistence
|
|
||||||
- Most important bench lesson: if the config UART appears dead, first retry with commands ending in `\n` instead of `\r\n`.
|
|
||||||
|
|
||||||
### Open Items
|
|
||||||
|
|
||||||
- Confirm whether `COM9` is the real `USART1` config port by live command-response evidence.
|
|
||||||
- If command-response is unstable, inspect whether host wiring/USB-UART level shifting is the cause before changing parser logic.
|
|
||||||
- If persistence fails after a clean `AT+SAVE`, inspect `App/flash_param.c` and raw Flash contents at `0x0800FC00` before changing higher-level config logic.
|
|
||||||
|
|
||||||
## 2026-03-31 CH390D Bring-up Debug Session
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
- Determine why `CH390D` does not return valid register values during boot.
|
|
||||||
- Find a software-side root cause if one exists and attempt a minimal fix.
|
|
||||||
|
|
||||||
### Baseline Symptom
|
|
||||||
|
|
||||||
- MCU boots normally and RTT works.
|
|
||||||
- CH390 boot diagnostics originally reported:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TCP2UART boot
|
|
||||||
CH390 VID=0x0000 PID=0x0000 REV=0x00 NSR=0x00 LINK=0
|
|
||||||
CH390 NCR=0x00 RCR=0x00 IMR=0x00 INTCR=0x00 GPR=0x00 ISR=0x00
|
|
||||||
CH390 WRCHK NCR:0x00->0x00 INTCR:0x00->0x00
|
|
||||||
```
|
|
||||||
|
|
||||||
- This showed that CH390 register reads and write-back checks were not producing valid values.
|
|
||||||
|
|
||||||
### Board-Side Evidence Already Collected
|
|
||||||
|
|
||||||
- `RST` line was observed released high.
|
|
||||||
- `CS` line idle state was high.
|
|
||||||
- `INT` line was observed low and mapped to EXTI.
|
|
||||||
- `SPI1` was enabled and configured for `Mode 3` in the active firmware.
|
|
||||||
- These observations did not by themselves restore valid CH390 responses.
|
|
||||||
|
|
||||||
### What Was Tried
|
|
||||||
|
|
||||||
1. Added richer RTT startup diagnostics in `BootDiag_ReportCh390()`.
|
|
||||||
2. Lowered `SPI1` speed from `/8` to `/64`.
|
|
||||||
3. Added stage markers around `low_level_init()` to localize the hang.
|
|
||||||
4. Step-debugged and breakpoint-debugged `ch390_default_config()` and `ch390_write_phy()`.
|
|
||||||
5. Added timeout protection to `ch390_read_phy()` / `ch390_write_phy()` so `EPCR` polling cannot hang forever.
|
|
||||||
6. Temporarily skipped `ch390_set_phy_mode(CH390_AUTO)` to isolate non-PHY register access.
|
|
||||||
7. Compared current driver against `Reference/EVT/EXAM/PUB/CH390.c` and `Reference/EVT/EXAM/PUB/CH390_Interface.c`.
|
|
||||||
8. Tried multiple SPI register transaction shapes:
|
|
||||||
- original two-byte exchange style
|
|
||||||
- split `Transmit` then read phase
|
|
||||||
- explicit dummy-byte read phase
|
|
||||||
- single-frame two-byte full-duplex read
|
|
||||||
9. Scanned all four SPI modes (`mode0`..`mode3`) during startup.
|
|
||||||
10. Added small `CS` setup/hold delays.
|
|
||||||
11. Increased hardware reset release wait to `50ms`.
|
|
||||||
12. Restored EVT-style init order and PHY setup path to see whether EVT sequence alone fixes the problem.
|
|
||||||
|
|
||||||
### Key Intermediate Findings
|
|
||||||
|
|
||||||
- Lowering SPI speed changed behavior, but did not recover valid CH390 IDs.
|
|
||||||
- Stage markers showed that low-speed SPI could stall during `ETH init: default`.
|
|
||||||
- Step/RTT evidence localized the original stall to PHY access during `ch390_default_config()`.
|
|
||||||
- The PHY access loop in `ch390_read_phy()` / `ch390_write_phy()` had no timeout and could hang indefinitely. This is a real software bug and should stay fixed.
|
|
||||||
- After adding PHY timeouts and temporarily skipping PHY setup, the init path completed, but all CH390 reads became `0xFF` rather than valid IDs.
|
|
||||||
- SPI mode scan result under that condition was:
|
|
||||||
|
|
||||||
```text
|
|
||||||
CH390 SPI mode0 [FF FF FF FF FF]
|
|
||||||
CH390 SPI mode1 [FF FF FF FF FF]
|
|
||||||
CH390 SPI mode2 [FF FF FF FF FF]
|
|
||||||
CH390 SPI mode3 [FF FF FF FF FF]
|
|
||||||
```
|
|
||||||
|
|
||||||
- This ruled out a simple `CPOL/CPHA` mismatch.
|
|
||||||
- External code comparison did not reveal an `opcode` or register-address mismatch. Public CH390 implementations use the same `OPC_REG_R=0x00`, `OPC_REG_W=0x80`, and the same register map.
|
|
||||||
- One experimental split transaction path produced repeatable but obviously bogus values like `0x03`, `0xAC`, `0xAE`, which strongly suggests transaction artifacts rather than real CH390 data.
|
|
||||||
- A debug read of `SPI1->SR` showed `OVR=1` during one of the experimental transaction variants, indicating the SPI transaction layer was not trustworthy in that configuration.
|
|
||||||
|
|
||||||
### EVT Comparison Outcome
|
|
||||||
|
|
||||||
- `Reference/EVT` is useful as a baseline, but it is not a drop-in fix for this project.
|
|
||||||
- The broad init order in the live project already matches EVT closely through the lwIP glue path.
|
|
||||||
- The most important EVT-specific difference is that EVT performs `ch390_set_phy_mode(CH390_AUTO)` at the start of `ch390_default_config()`.
|
|
||||||
- Restoring the EVT-style `PHY` setup path in this project caused boot to hang again at:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TCP2UART boot
|
|
||||||
ETH init: gpio
|
|
||||||
ETH init: spi
|
|
||||||
ETH init: reset
|
|
||||||
ETH init: default
|
|
||||||
```
|
|
||||||
|
|
||||||
- That confirms the `PHY` path is a real trigger for the hang, but EVT order alone does not solve the underlying communication problem.
|
|
||||||
|
|
||||||
### Current Best Technical Conclusion
|
|
||||||
|
|
||||||
- A real software defect was found and fixed: `EPCR` polling in PHY access had no timeout.
|
|
||||||
- That fix prevents the firmware from hanging forever, but it does **not** restore valid CH390 register communication.
|
|
||||||
- The core unresolved problem remains: the SPI register-access path still does not yield believable CH390 register data.
|
|
||||||
- At this point, the following common explanations have already been tested and are **not** sufficient by themselves:
|
|
||||||
- SPI mode selection
|
|
||||||
- adding dummy bytes
|
|
||||||
- `CS` setup/hold delays
|
|
||||||
- changing reset wait from `10ms` to `50ms`
|
|
||||||
- reverting to EVT transaction style
|
|
||||||
- restoring EVT initialization order
|
|
||||||
- public `opcode` / register-map mismatch
|
|
||||||
|
|
||||||
### Recommended Next Debug Step
|
|
||||||
|
|
||||||
- The next high-value experiment is a temporary GPIO bit-bang read of `VIDL/VIDH/CHIPR` with a fully controlled continuous command+clock sequence.
|
|
||||||
- If bit-bang returns valid IDs while HAL-SPI paths do not, the remaining fault is in the SPI transaction implementation rather than CH390 higher-level init order.
|
|
||||||
- If bit-bang still returns invalid data, the investigation must move back to board-level bus behavior even if static continuity checks look correct.
|
|
||||||
|
|
||||||
### Additional 2026-03-31 Finding: HAL SPI Re-init And Bit-Bang Side Effects
|
|
||||||
|
|
||||||
- A valid concern was raised about calling `HAL_SPI_Init()` after temporarily changing SPI pins to GPIO mode.
|
|
||||||
- Code review of `stm32f1xx_hal_spi.c` showed that `HAL_SPI_MspInit()` only runs when `hspi->State == HAL_SPI_STATE_RESET`.
|
|
||||||
- Therefore, simply calling `HAL_SPI_Init()` after bit-bang mode does **not** automatically restore `PA5/PA7` to SPI alternate-function output mode.
|
|
||||||
- This was a real software-side risk in the temporary bit-bang probe and was corrected by explicitly restoring:
|
|
||||||
- `PA5` -> `AF_PP`
|
|
||||||
- `PA7` -> `AF_PP`
|
|
||||||
- `PA6` -> input
|
|
||||||
before calling `HAL_SPI_Init()` again.
|
|
||||||
- After that correction, the observed behavior changed again: boot output stopped at `ETH init: reset`, and a short halt showed execution inside `HAL_SPI_TransmitReceive()` called from the CH390 SPI exchange path.
|
|
||||||
- This means the earlier bit-bang experiments could have polluted later SPI results, but after the GPIO restore fix, the active blocker is again a live SPI transaction stall rather than a missing-GPIO-restore artifact.
|
|
||||||
|
|
||||||
### Additional 2026-03-31 Finding: Reset Exists In Runtime Path
|
|
||||||
|
|
||||||
- The project does **not** lack a CH390 reset process.
|
|
||||||
- The actual runtime order is:
|
|
||||||
1. `App_Init()`
|
|
||||||
2. `lwip_netif_init()`
|
|
||||||
3. `ethernetif_init()`
|
|
||||||
4. `low_level_init()`
|
|
||||||
5. `ch390_gpio_init()`
|
|
||||||
6. `ch390_spi_init()`
|
|
||||||
7. `ch390_hardware_reset()`
|
|
||||||
8. `ch390_default_config()`
|
|
||||||
- The reset process is therefore present and executed, but it lives in the lwIP/netif bring-up path instead of being written inline in `main.c` as in the EVT sample.
|
|
||||||
- The current unresolved problem is not "missing reset"; it is that SPI transactions after reset still do not produce valid CH390 register responses.
|
|
||||||
|
|
||||||
## 2026-03-31 Manual Reset Sensitivity Analysis
|
|
||||||
|
|
||||||
### Observed Symptom
|
|
||||||
|
|
||||||
- An extra `ch390_hardware_reset()` was temporarily inserted into `App_Init()` before `lwip_init()`.
|
|
||||||
- With that extra reset in place, a manual board reset could lead to the firmware appearing stuck and the LED heartbeat not behaving normally.
|
|
||||||
- The same image could still look more normal when observed after a `probe-rs` flash-and-run cycle.
|
|
||||||
|
|
||||||
### Code-Level Finding
|
|
||||||
|
|
||||||
- The inserted extra reset sat here in `Core/Src/main.c`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
SEGGER_RTT_Init();
|
|
||||||
SEGGER_RTT_WriteString(0, "\r\nTCP2UART boot\r\n");
|
|
||||||
...
|
|
||||||
ch390_hardware_reset();
|
|
||||||
lwip_init();
|
|
||||||
lwip_netif_init(...);
|
|
||||||
```
|
|
||||||
|
|
||||||
- But the normal bring-up path already performs a reset later inside `ch390_runtime_init()` / `low_level_init()` before `ch390_default_config()`.
|
|
||||||
- That means the temporary line created a redundant early reset in a different initialization phase than the normal driver-owned reset.
|
|
||||||
|
|
||||||
### Interpretation
|
|
||||||
|
|
||||||
- This pattern is much more consistent with a reset-sequencing / startup-state issue than with compiler optimization level.
|
|
||||||
- The Keil target uses one fixed optimization configuration, so a plain manual reset does not change code generation.
|
|
||||||
- In contrast, an extra CH390 reset inserted before lwIP and before the normal CH390 runtime init can alter the device startup state and timing relationship between the MCU and CH390.
|
|
||||||
|
|
||||||
### Action Taken
|
|
||||||
|
|
||||||
- The extra `ch390_hardware_reset()` in `App_Init()` was removed.
|
|
||||||
- The firmware now relies only on the standard driver-owned reset inside the CH390 runtime initialization path.
|
|
||||||
|
|
||||||
### Conclusion
|
|
||||||
|
|
||||||
- The temporary extra reset was not kept.
|
|
||||||
- The strongest software-side conclusion is that the manual-reset sensitivity was caused by redundant reset sequencing rather than by optimization level.
|
|
||||||
|
|
||||||
## 2026-03-31 HardFault Root Cause And Fix
|
|
||||||
|
|
||||||
### Symptom
|
|
||||||
|
|
||||||
- After CH390 bring-up completed and boot diagnostics printed, the firmware entered:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TRAP: HardFault_Handler
|
|
||||||
```
|
|
||||||
|
|
||||||
- At the same time, `PC13` stopped blinking, which originally looked like a timer or LED problem.
|
|
||||||
|
|
||||||
### Fault Evidence
|
|
||||||
|
|
||||||
- Fault-status registers showed a real fault rather than a normal busy wait.
|
|
||||||
- The trap location was `Debug_TrapWithRttHint()` in `Core/Src/main.c`.
|
|
||||||
- The stacked fault frame pointed back into the normal runtime path rather than the trap itself.
|
|
||||||
- `TIM4` was configured and had already advanced `g_led_blink_ticks`, so the LED path was alive before the fault.
|
|
||||||
|
|
||||||
### Root Cause
|
|
||||||
|
|
||||||
- `MX_IWDG_Init()` had been temporarily commented out in `main()`.
|
|
||||||
- However, `App_Poll()` still executed:
|
|
||||||
|
|
||||||
```c
|
|
||||||
HAL_IWDG_Refresh(&hiwdg);
|
|
||||||
```
|
|
||||||
|
|
||||||
- Because `hiwdg` was never initialized, this call operated on an invalid handle and led to the observed fault path.
|
|
||||||
|
|
||||||
### Fix Applied
|
|
||||||
|
|
||||||
- `Core/Src/main.c` was changed so watchdog refresh only runs when `hiwdg.Instance == IWDG`.
|
|
||||||
- This preserves normal behavior when IWDG is enabled, while avoiding invalid access when IWDG init is intentionally disabled for debugging.
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
- Rebuilt successfully with `0 error`, `1 warning`.
|
|
||||||
- Reflashed target and reran startup.
|
|
||||||
- Boot RTT still showed CH390 diagnostics, but no longer showed `TRAP: HardFault_Handler`.
|
|
||||||
- A 5-second runtime window completed without a new trap.
|
|
||||||
- `g_led_blink_ticks` continued advancing after the fix, confirming that `TIM4` interrupts and the LED heartbeat path were alive again.
|
|
||||||
|
|
||||||
### Conclusion
|
|
||||||
|
|
||||||
- The HardFault was caused by refreshing an uninitialized IWDG handle, not by the CH390 SPI path itself.
|
|
||||||
- This issue is fixed.
|
|
||||||
- CH390 bring-up is still unresolved at the register-communication level, but the main task is again able to continue running normally.
|
|
||||||
|
|
||||||
## 2026-03-31 Runtime Freeze Root Cause And Fix
|
|
||||||
|
|
||||||
### Symptom
|
|
||||||
|
|
||||||
- After re-soldering CH390D, the system could boot and print the normal CH390 startup diagnostics.
|
|
||||||
- However, after running for a while, the device would appear to freeze.
|
|
||||||
- In that state, the LED heartbeat behavior became unreliable and the system appeared to stop making useful progress.
|
|
||||||
|
|
||||||
### Key Runtime Evidence
|
|
||||||
|
|
||||||
- The new freeze was **not** another HardFault: no new `TRAP:` line appeared during the freeze window.
|
|
||||||
- `g_led_blink_ticks` continued advancing during observation windows, proving that `TIM4` interrupts were still alive and the MCU was not fully dead.
|
|
||||||
- A short halt during the bad behavior repeatedly landed in `HAL_SPI_TransmitReceive()`.
|
|
||||||
- Code inspection showed that CH390 runtime paths in `ethernetif.c` were executing blocking SPI transactions while global interrupts were disabled via `ethernetif_lock()`.
|
|
||||||
|
|
||||||
### Root Cause
|
|
||||||
|
|
||||||
- `low_level_output()`, `low_level_input()`, and `ethernetif_check_link()` in `Drivers/LwIP/src/netif/ethernetif.c` wrapped CH390 SPI register/memory accesses inside `ethernetif_lock()` / `ethernetif_unlock()`.
|
|
||||||
- Those helpers globally disable interrupts by manipulating `PRIMASK`.
|
|
||||||
- The CH390 access path uses blocking HAL SPI functions and timeout logic based on `HAL_GetTick()`.
|
|
||||||
- Running those blocking accesses with interrupts disabled can stall or livelock the runtime path, especially after startup when network polling begins.
|
|
||||||
|
|
||||||
### Fix Applied
|
|
||||||
|
|
||||||
- Reduced the interrupt-masked critical sections in `ethernetif.c` to only protect the shared IRQ-pending flag.
|
|
||||||
- Removed `ethernetif_lock()` coverage from the long CH390 SPI transaction paths in:
|
|
||||||
- `low_level_output()`
|
|
||||||
- `low_level_input()`
|
|
||||||
- `ethernetif_check_link()`
|
|
||||||
- In `ethernetif_poll()`, only the `g_ch390_irq_pending` flag is now cleared under the short critical section; the actual CH390 register access happens with interrupts enabled.
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
- Rebuilt successfully with `0 error`, `0 warning`.
|
|
||||||
- Reflashed and reran the target.
|
|
||||||
- Boot RTT still completed normally through:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TCP2UART boot
|
|
||||||
ETH init: gpio
|
|
||||||
ETH init: spi
|
|
||||||
ETH init: reset
|
|
||||||
ETH init: default
|
|
||||||
ETH init: mac
|
|
||||||
ETH init: getmac
|
|
||||||
ETH init: irq
|
|
||||||
ETH init: done
|
|
||||||
CH390 VID=0x0000 PID=0x0000 REV=0x00 NSR=0x00 LINK=0
|
|
||||||
CH390 NCR=0x00 RCR=0x00 IMR=0x00 INTCR=0x00 GPR=0x00 ISR=0x00
|
|
||||||
```
|
|
||||||
|
|
||||||
- No new `TRAP:` message appeared during extended runtime observation.
|
|
||||||
- `g_led_blink_ticks` continued advancing over multiple samples, indicating that the heartbeat timer and interrupt delivery remained active.
|
|
||||||
- The system no longer reproduced the earlier “runs for a while then appears frozen” behavior in the observed validation window.
|
|
||||||
|
|
||||||
### Conclusion
|
|
||||||
|
|
||||||
- This freeze was caused by doing blocking CH390 SPI operations inside a global interrupt-disabled critical section.
|
|
||||||
- The runtime freeze is fixed.
|
|
||||||
- CH390 register communication is still invalid (`0x0000` ID values), but that is now a separate communication/bring-up problem rather than the cause of the observed runtime stall.
|
|
||||||
|
|
||||||
## 2026-03-31 SPI Ownership Decoupling And CH390 Current Status
|
|
||||||
|
|
||||||
### Why This Refactor Was Done
|
|
||||||
|
|
||||||
- The project previously allowed multiple runtime layers to reach down into CH390/SPI behavior directly:
|
|
||||||
- `ethernetif.c` handled init, IRQ-driven poll service, RX/TX transactions, and link checks
|
|
||||||
- `main.c` directly read CH390 registers for boot diagnostics
|
|
||||||
- the CH390 low-level SPI transport sat underneath those callers with no single runtime owner boundary
|
|
||||||
- This made the system harder to reason about and contributed to runtime instability when CH390 accesses happened from different code paths with different assumptions.
|
|
||||||
|
|
||||||
### Refactor Outcome
|
|
||||||
|
|
||||||
- Added a single runtime owner module: `Drivers/CH390/ch390_runtime.c` + `Drivers/CH390/ch390_runtime.h`.
|
|
||||||
- After this change:
|
|
||||||
- `CH390_Interface.c` remains the **only** SPI transport implementation
|
|
||||||
- `CH390.c` remains the chip-level helper layer
|
|
||||||
- `ch390_runtime.c` is now the **only runtime owner** of CH390 transactions after boot
|
|
||||||
- `ethernetif.c` delegates runtime TX/RX/link/IRQ servicing to `ch390_runtime`
|
|
||||||
- `main.c` no longer performs direct CH390 register reads; boot diagnostics use `ch390_runtime_get_diag()`
|
|
||||||
- `EXTI0_IRQHandler()` only posts the IRQ-pending event into the runtime owner and does not touch CH390 directly
|
|
||||||
|
|
||||||
### Behavior After Refactor
|
|
||||||
|
|
||||||
- Build passed with `0 error`, `0 warning`.
|
|
||||||
- The system remained stable in the post-refactor runtime window:
|
|
||||||
- no new trap output
|
|
||||||
- heartbeat/timer activity continued
|
|
||||||
- previous runtime freeze did not reproduce in the observed window
|
|
||||||
|
|
||||||
### CH390 Result After Refactor
|
|
||||||
|
|
||||||
- The CH390 did **not** come up successfully.
|
|
||||||
- However, the failure signature became cleaner and more trustworthy:
|
|
||||||
|
|
||||||
```text
|
|
||||||
CH390 VID=0xFFFF PID=0xFFFF REV=0xFF NSR=0xFF LINK=1
|
|
||||||
CH390 NCR=0xFF RCR=0xFF IMR=0xFF INTCR=0xFF GPR=0xFF ISR=0xFF
|
|
||||||
```
|
|
||||||
|
|
||||||
- This is materially different from the earlier unstable mixture of:
|
|
||||||
- all-zero reads
|
|
||||||
- intermittent hangs
|
|
||||||
- transaction artifacts
|
|
||||||
- watchdog-related HardFaults
|
|
||||||
|
|
||||||
### Trusted Interpretation Of Current Failure
|
|
||||||
|
|
||||||
- With the SPI access model cleaned up and the system remaining stable, the current CH390 failure can now be treated as a **credible transport-level non-response** rather than a concurrency artifact.
|
|
||||||
- A uniform `0xFF` readback across identity and status/control registers strongly suggests one of these conditions:
|
|
||||||
- CH390 still does not actively drive MISO during the register-read phase
|
|
||||||
- CS reaches the MCU logic but is not effectively selecting the CH390 device on the board side
|
|
||||||
- the CH390 digital core is not entering a valid SPI-responding state after reset even though the MCU-side sequence now looks consistent
|
|
||||||
|
|
||||||
### Practical Conclusion
|
|
||||||
|
|
||||||
- The architectural decoupling requirement is complete.
|
|
||||||
- The runtime stability requirement is complete.
|
|
||||||
- CH390 connection is **still failed**, but the reason is now narrowed to a believable low-level bus/device-response problem rather than a software ownership/concurrency problem.
|
|
||||||
|
|
||||||
## 2026-04-01 Final Software Boundary Check: Hardware SPI vs Bit-Bang
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
- Decide whether another software-side SPI transaction rewrite is still justified.
|
|
||||||
- Use one last high-signal experiment to separate STM32 hardware-SPI issues from board-level CH390 non-response.
|
|
||||||
|
|
||||||
### Experiment
|
|
||||||
|
|
||||||
- Kept the normal hardware-SPI identity probe in `ch390_runtime_probe_identity()`.
|
|
||||||
- Added a temporary bit-bang register read helper in `Drivers/CH390/CH390_Interface.c` that:
|
|
||||||
- disables `SPI1`
|
|
||||||
- reconfigures `PA5/PA7` as GPIO outputs and `PA6` as GPIO input
|
|
||||||
- clocks out register reads in software with explicit `CS/SCK/MOSI/MISO` control
|
|
||||||
- restores the pins back to hardware-SPI mode before returning
|
|
||||||
- On hardware-SPI probe failure, startup now prints one additional RTT line with bit-bang reads of:
|
|
||||||
- `VIDL`
|
|
||||||
- `VIDH`
|
|
||||||
- `PIDL`
|
|
||||||
- `PIDH`
|
|
||||||
- `CHIPR`
|
|
||||||
|
|
||||||
### Observed RTT Output
|
|
||||||
|
|
||||||
```text
|
|
||||||
TCP2UART boot
|
|
||||||
ETH init: gpio
|
|
||||||
ETH init: spi
|
|
||||||
ETH init: reset
|
|
||||||
ETH init: probe
|
|
||||||
CH390 bitbang VIDL=0xFF VIDH=0xFF PIDL=0xFF PIDH=0xFF CHIPR=0xFF
|
|
||||||
ETH init: invalid chip id
|
|
||||||
CH390 VID=0xFFFF PID=0xFFFF REV=0xFF NSR=0xFF LINK=0
|
|
||||||
CH390 NCR=0xFF RCR=0xFF IMR=0xFF INTCR=0xFF GPR=0xFF ISR=0xFF
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interpretation
|
|
||||||
|
|
||||||
- The MCU is alive, RTT is alive, and the firmware reaches the CH390 identity-probe stage normally.
|
|
||||||
- The normal hardware-SPI read path still returns all `0xFF`.
|
|
||||||
- The independent bit-bang read path also returns all `0xFF` for the same critical identity registers.
|
|
||||||
- This is the most important final discriminator collected so far:
|
|
||||||
- if hardware SPI alone were the remaining problem, bit-bang should have had a realistic chance to return valid IDs
|
|
||||||
- because both methods return the same all-`0xFF` result, the remaining fault is much more consistent with CH390-side non-response than with STM32 SPI peripheral behavior
|
|
||||||
|
|
||||||
### External Reference Cross-Check
|
|
||||||
|
|
||||||
- Public working CH390/DM9051 drivers commonly use `SPI mode 0`, `1-bit command + 7-bit address` framing, and contiguous packet-memory bursts.
|
|
||||||
- Those references support further cleanup of packet-memory transaction framing once basic register access works.
|
|
||||||
- They do **not** make packet-memory fragmentation a strong explanation for `VID/PID/basic regs = 0xFFFF/0xFF` from the very start.
|
|
||||||
- Therefore the remaining software-side suspects are now weaker than the board-level suspects.
|
|
||||||
|
|
||||||
### Final Conclusion
|
|
||||||
|
|
||||||
- The project has already removed several real software defects and sources of diagnostic noise:
|
|
||||||
- PHY access timeout hole
|
|
||||||
- watchdog-related HardFault
|
|
||||||
- interrupt-masked blocking SPI runtime path
|
|
||||||
- redundant early CH390 reset in `main()`
|
|
||||||
- warning-producing dead code / unused values
|
|
||||||
- After those fixes, both hardware-SPI and bit-bang reads still show CH390 register non-response.
|
|
||||||
- The most defensible current conclusion is:
|
|
||||||
- **software is no longer the primary blocker**
|
|
||||||
- the remaining fault is more likely in board wiring, chip power/reset state, effective CS selection, MISO drive, signal integrity, or the CH390D device itself
|
|
||||||
|
|
||||||
### Recommended Next Step Outside Firmware
|
|
||||||
|
|
||||||
- Probe real waveforms on `CS/SCK/MOSI/MISO/RST/INT` during the identity-read transaction.
|
|
||||||
- Specifically verify that:
|
|
||||||
- `CS` actually reaches the CH390 pin and stays low for the transaction
|
|
||||||
- `RST` is released high at the chip pin
|
|
||||||
- `MISO` is actively driven by the CH390 instead of floating high
|
|
||||||
- the CH390 supply and reset domain are valid during the read window
|
|
||||||
+1159
File diff suppressed because it is too large
Load Diff
@@ -90,9 +90,8 @@
|
|||||||
- 配置口
|
- 配置口
|
||||||
- 负责接收 `AT` 命令
|
- 负责接收 `AT` 命令
|
||||||
- 当前接收逻辑在:
|
- 当前接收逻辑在:
|
||||||
- `Core/Src/main.c` 的 `App_PollUart1ConfigRx()`
|
|
||||||
- `Core/Src/stm32f1xx_it.c` 的 `HAL_UART_RxCpltCallback()`
|
- `Core/Src/stm32f1xx_it.c` 的 `HAL_UART_RxCpltCallback()`
|
||||||
- `App/config.c` 的 `config_uart_rx_byte()` / `config_process_at_cmd()`
|
- `App/config.c` 的 `config_uart_rx_byte()` / `config_poll()` / `config_process_at_cmd()`
|
||||||
2. `USART2 / USART3`
|
2. `USART2 / USART3`
|
||||||
- 数据口
|
- 数据口
|
||||||
- 负责普通透传或 MUX 承载
|
- 负责普通透传或 MUX 承载
|
||||||
@@ -245,10 +244,11 @@
|
|||||||
|
|
||||||
根据已有联调记录,配置口最关键的 bench 规则是:
|
根据已有联调记录,配置口最关键的 bench 规则是:
|
||||||
|
|
||||||
1. 当前现场验证时,配置命令必须保证以换行完成帧。
|
1. 对外手册统一要求 AT 文本以 `\r\n` 结束,现场工具也应优先按这个格式发送。
|
||||||
2. 若主机侧发送方式不对,现象会很像“配置口完全无响应”。
|
2. 若主机侧发送方式不对,现象会很像“配置口完全无响应”。
|
||||||
3. 因此,配置口不响应时,第一优先级不是改 parser,而是先验证主机端发送格式与接线。
|
3. 因此,配置口不响应时,第一优先级不是改 parser,而是先验证主机端发送格式与接线。
|
||||||
4. `BAUD` 类命令若查询值已变化,但 `USART2/USART3` 现场波特率尚未变化,不应立即归因为命令无效,应先确认是否已经执行 `AT+SAVE` 与 `AT+RESET`。
|
4. 当前实现可能容忍只以 `\n` 结束的输入,但这只是接收实现细节,不作为对外协议口径。
|
||||||
|
5. `BAUD` 类命令若查询值已变化,但 `USART2/USART3` 现场波特率尚未变化,不应立即归因为命令无效,应先确认是否已经执行 `AT+SAVE` 与 `AT+RESET`。
|
||||||
|
|
||||||
### 6.3 最小验证步骤
|
### 6.3 最小验证步骤
|
||||||
|
|
||||||
@@ -629,9 +629,9 @@ RTT输出:
|
|||||||
1. `AT固件使用手册.md`
|
1. `AT固件使用手册.md`
|
||||||
2. `项目技术实现.md`
|
2. `项目技术实现.md`
|
||||||
3. `项目需求说明.md`
|
3. `项目需求说明.md`
|
||||||
4. `uart-ch390-debug-handoff.md`
|
4. `代码结构与阅读指南.md`
|
||||||
5. `CH390_最终结论报告.md`
|
5. `项目文档索引.md`
|
||||||
6. `build_keil.log`
|
6. `CH390_最终结论报告.md`
|
||||||
7. `PCB/SCH_Schematic1_2026-03-26.pdf`
|
7. `PCB/SCH_Schematic1_2026-03-26.pdf`
|
||||||
8. `tools/tcp_debug_server.py`
|
8. `tools/tcp_debug_server.py`
|
||||||
9. `tools/start_tcp_debug_server.ps1`
|
9. `tools/start_tcp_debug_server.ps1`
|
||||||
|
|||||||
@@ -184,21 +184,134 @@ EN,LPORT,RIP,RPORT,UART
|
|||||||
2. 统一受 `LINK[idx]` 配置驱动
|
2. 统一受 `LINK[idx]` 配置驱动
|
||||||
3. 由调度层决定实例与 UART 的数据交换路径
|
3. 由调度层决定实例与 UART 的数据交换路径
|
||||||
|
|
||||||
## 七、主循环实现方向
|
### 6.4 `v1.1.0` 低 RAM TCP 背压修复
|
||||||
|
|
||||||
|
`v1.1.0` 起,`TCP -> UART` 路径补充如下实现约束,用于解决“TCP 接收过快、UART 发送过慢时本地缓存被冲垮”的问题,同时尽量不新增静态 RAM:
|
||||||
|
|
||||||
|
1. 继续复用 `tcp_server` / `tcp_client` 现有 `RX ring`,不为每个连接新增独立的大块 pending payload 缓冲。
|
||||||
|
2. `tcp_server_on_recv()` / `tcp_client_on_recv()` 不再在回调内立即 `tcp_recved()`。
|
||||||
|
3. lwIP 交来的 `pbuf` 在回调中通过 `pbuf_ref()` 转为应用持有,再释放回调上下文的原始引用;后续由应用在主循环中继续把数据泵入 `RX ring`,最终在消费完成后释放。
|
||||||
|
4. 当 `RX ring` 暂时装不下时,剩余数据保留在 `hold_pbuf + hold_offset` 中,等待主循环下一轮继续搬运。
|
||||||
|
5. 只有当数据真正从 `TCP RX ring` 被 `drop` 掉,也就是已经被下游 `UART` 接收进入发送路径时,才调用 `tcp_recved()` 释放 TCP 接收窗口。
|
||||||
|
|
||||||
|
这样做的效果是:
|
||||||
|
|
||||||
|
1. `UART` 慢时,TCP 窗口不会继续无条件放大。
|
||||||
|
2. 对端发送速度会被 lwIP 接收窗口自然压制。
|
||||||
|
3. 修复点建立在已有 ring 与主循环调度之上,不引入 `FreeRTOS` 或新的大块静态缓存。
|
||||||
|
|
||||||
|
#### RAW 与 MUX 的分流规则
|
||||||
|
|
||||||
|
在 `v1.1.0` 中,`TCP` 侧拿到的都是纯 payload,因此 `TCP` 背压逻辑在 `RAW` 与 `MUX` 两种模式下共用到 `UART commit` 之前:
|
||||||
|
|
||||||
|
1. `RAW` 模式:
|
||||||
|
- 主循环先查看 `uart_trans_tx_free()`
|
||||||
|
- 再按 `min(tcp_available, tx_free, APP_TCP_TO_UART_CHUNK_SIZE)` 从 TCP ring `peek`
|
||||||
|
- `uart_trans_write()` 实际写入多少,就 `drop + tcp_recved` 多少
|
||||||
|
2. `MUX` 模式:
|
||||||
|
- `TCP` payload 本身不带帧头尾
|
||||||
|
- 只有当 `UART TX free >= payload_len + 6` 时,才在栈上临时编码一帧并一次性写入 `UART TX ring`
|
||||||
|
- 只有整帧成功入队后,才按原始 payload 长度执行 `drop + tcp_recved`
|
||||||
|
|
||||||
|
该设计保证:
|
||||||
|
|
||||||
|
1. `RAW` 模式允许流式逐步提交
|
||||||
|
2. `MUX` 模式保持“单个 UART 输出帧必须完整入队”的语义
|
||||||
|
3. `TCP` 接收窗口始终以真实下游消费进度为准,而不是以“回调里已经 memcpy 到本地”作为提交点
|
||||||
|
|
||||||
|
#### RAM 与 chunk 策略
|
||||||
|
|
||||||
|
为给新增的 `hold_pbuf / hold_offset` 状态字段让位,并进一步降低单轮转发压力,`v1.1.0` 同步采用以下策略:
|
||||||
|
|
||||||
|
1. 新增 `APP_TCP_TO_UART_CHUNK_SIZE = 128`
|
||||||
|
2. `TCP_SERVER_RX_BUFFER_SIZE` 从 `512` 调整为 `480`
|
||||||
|
3. `TCP_CLIENT_RX_BUFFER_SIZE` 从 `512` 调整为 `480`
|
||||||
|
|
||||||
|
设计意图:
|
||||||
|
|
||||||
|
1. 利用更小的单次转发块提升主循环调度颗粒度
|
||||||
|
2. 让 `MUX` 模式下 `payload + 6` 更容易完整进入 `UART TX ring`
|
||||||
|
3. 在静态 RAM 已接近上限时,为少量新状态字段回收空间
|
||||||
|
|
||||||
|
#### 构建基线
|
||||||
|
|
||||||
|
`v1.1.0` 以 `MDK-ARM/TCP2UART.uvprojx` 的 `TCP2UART` Target 为构建验收基线。
|
||||||
|
|
||||||
|
当前一次通过的参考结果:
|
||||||
|
|
||||||
|
1. `errors = 0`
|
||||||
|
2. `warnings = 0`
|
||||||
|
3. `flash_bytes = 56544`
|
||||||
|
4. `ram_bytes = 20376`
|
||||||
|
|
||||||
|
该结果说明修复后工程仍满足 `STM32F103R8T6` 的 `20KB RAM` 上限,但余量已经很小;后续若继续增加功能,应优先考虑复用现有缓冲与状态,而不是增加新的静态大数组。
|
||||||
|
|
||||||
|
### 6.3 客户现场脏网络恢复增强
|
||||||
|
|
||||||
|
客户现场换 PC 后曾出现设备持续 ARP、`ping` 不通、TCP Client 不恢复的现象。抓包显示故障前后存在 IPv6、DHCPv6、mDNS、IGMP、LLDP 等与当前业务无关的网络噪声;当前固件为静态 IPv4、TCP2UART 与 ICMP 诊断模型,不依赖 UDP、IPv6、DHCP 或多播发现。
|
||||||
|
|
||||||
|
本阶段采用低 RAM 优先的恢复策略,不先扩大 `PBUF_POOL_SIZE`,而是在更靠近入口的位置减少无关帧对 lwIP pool 的占用:
|
||||||
|
|
||||||
|
1. `TCP Client CONNECTING` 增加应用层超时:
|
||||||
|
- `TCP_CLIENT_CONNECT_TIMEOUT_MS = 10000`
|
||||||
|
- `tcp_connect()` 返回 `ERR_OK` 后记录 `connect_start_ms`
|
||||||
|
- `tcp_client_poll()` 发现 `CONNECTING` 超过超时时间后,注销回调、`tcp_abort()` 当前 PCB、释放 `hold_pbuf`,再按原有 `reconnect_interval_ms` 重连
|
||||||
|
- `tcp_client_status_t.connect_timeout_count` 记录发生次数
|
||||||
|
2. `CH390` RX 入口增加 `pre-pbuf` 协议过滤:
|
||||||
|
- 在 `pbuf_alloc(PBUF_RAW, ..., PBUF_POOL)` 之前先读取以太网头与最小 IPv4 头
|
||||||
|
- 允许进入 lwIP 的协议限定为 `ARP`、`IPv4 ICMP`、`IPv4 TCP`
|
||||||
|
- 默认丢弃 `IPv6`、`IPv4 UDP`、`IPv4 IGMP`、`LLDP`、未知 EtherType 与畸形头
|
||||||
|
- 丢弃帧只跳过 CH390 RX FIFO 剩余字节,不分配 pbuf
|
||||||
|
3. 软件 MAC 过滤暂不启用:
|
||||||
|
- 第一版只做协议层过滤,避免误杀广播 ARP 或未来硬件过滤策略变化
|
||||||
|
- 目的 MAC 相关判断保留为后续可选增强
|
||||||
|
|
||||||
|
新增 CH390 诊断字段用于现场判断是否仍存在资源压力:
|
||||||
|
|
||||||
|
1. `rx_pbuf_alloc_failed`
|
||||||
|
2. `rx_filtered_frames`
|
||||||
|
3. `rx_filtered_ipv6`
|
||||||
|
4. `rx_filtered_udp`
|
||||||
|
5. `rx_filtered_igmp`
|
||||||
|
6. `rx_filtered_lldp`
|
||||||
|
7. `rx_filtered_other_eth`
|
||||||
|
8. `rx_filtered_other_ipv4`
|
||||||
|
9. `rx_filtered_malformed`
|
||||||
|
10. `rx_ipv4_icmp_frames`
|
||||||
|
11. `rx_ipv4_tcp_frames`
|
||||||
|
12. `rx_ipv4_udp_frames`
|
||||||
|
|
||||||
|
验收口径:
|
||||||
|
|
||||||
|
1. 正常 `ARP / ping / TCP Server / TCP Client` 功能不受影响
|
||||||
|
2. 客户脏网络中的 IPv6、UDP、IGMP、LLDP 噪声不会进入 lwIP pbuf pool
|
||||||
|
3. 若远端 TCP Server 不监听或静默丢 SYN,TCP Client 不再永久停留在 `CONNECTING`
|
||||||
|
4. 若过滤后 `rx_pbuf_alloc_failed` 仍持续增长,再评估从无关功能中回收 RAM 并调整 `PBUF_POOL_SIZE`
|
||||||
|
|
||||||
|
本阶段 Keil 构建验收结果:
|
||||||
|
|
||||||
|
1. `errors = 0`
|
||||||
|
2. `warnings = 0`
|
||||||
|
3. `flash_bytes = 57404`
|
||||||
|
4. `ram_bytes = 20440`
|
||||||
|
|
||||||
|
## 七、主循环实现路径
|
||||||
|
|
||||||
主循环仍保持裸机轮询风格:
|
主循环仍保持裸机轮询风格:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
|
config_poll();
|
||||||
|
uart_trans_poll();
|
||||||
ethernetif_poll();
|
ethernetif_poll();
|
||||||
ethernetif_check_link();
|
ethernetif_check_link();
|
||||||
sys_check_timeouts();
|
sys_check_timeouts();
|
||||||
tcp_link_poll();
|
App_StopLinksIfNeeded();
|
||||||
uart_mux_poll();
|
App_StartLinksIfNeeded();
|
||||||
config_poll();
|
App_RouteRawUartTraffic();
|
||||||
|
App_RouteMuxUartTraffic();
|
||||||
route_dispatch();
|
App_RouteTcpTraffic();
|
||||||
|
|
||||||
if (reset_requested) {
|
if (reset_requested) {
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
@@ -206,11 +319,11 @@ while (1)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
下一阶段实现要求:
|
当前实现要求:
|
||||||
|
|
||||||
1. 统一由 `LINK[idx]` 驱动实例状态
|
1. 统一由 `LINK[idx]` 驱动实例状态
|
||||||
2. 统一由 `MUX` 决定数据口承载模式
|
2. 统一由 `MUX` 决定数据口承载模式
|
||||||
3. 统一由 `route_dispatch()` 按 `SRCID / DSTMASK` 分发
|
3. RAW 路由与 MUX 路由分别由 `App_RouteRawUartTraffic()`、`App_RouteMuxUartTraffic()`、`App_RouteTcpTraffic()` 执行
|
||||||
|
|
||||||
## 八、实现边界
|
## 八、实现边界
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# TCP2UART 项目文档索引
|
||||||
|
|
||||||
|
本文档用于说明当前仓库中应长期维护的项目文档,以及已合并或删除的过时资料归属。
|
||||||
|
|
||||||
|
## 当前有效文档
|
||||||
|
|
||||||
|
| 文档 | 用途 | 阅读时机 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `项目需求说明.md` | 需求源头,定义硬件边界、软件边界、最终协议模型和验收口径 | 立项、需求确认、验收前 |
|
||||||
|
| `AT固件使用手册.md` | 对外 AT 协议和 MUX 帧使用说明 | 上位机开发、联调、测试脚本编写 |
|
||||||
|
| `项目技术实现.md` | 内部实现口径,说明配置模型、路由层、TCP 背压和网络链路策略 | 修改固件架构或核心逻辑前 |
|
||||||
|
| `代码结构与阅读指南.md` | 代码目录、主流程、模块职责和推荐阅读路径 | 新成员接手、代码审查、定位问题前 |
|
||||||
|
| `工程调试指南.md` | 实机 bring-up、串口、CH390、lwIP、TCP/UART 通路调试步骤 | 现场调试、故障复现、回归验证 |
|
||||||
|
| `CH390_最终结论报告.md` | CH390 阶段性硬件/软件排障结论归档 | 遇到 CH390 低层异常时回看历史结论 |
|
||||||
|
|
||||||
|
## 非项目叙述文档
|
||||||
|
|
||||||
|
| 文件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `Reference/stm32f103r8.pdf` | STM32F103R8 参考资料 |
|
||||||
|
| `Reference/CH390DS1.PDF` | CH390D 数据手册 |
|
||||||
|
| `TCP2UART.ioc` | STM32CubeMX 外设、时钟、DMA、引脚配置源 |
|
||||||
|
| `MDK-ARM/TCP2UART.uvprojx` | Keil MDK 主工程文件 |
|
||||||
|
| `CMakeLists.txt`、`cmake/stm32cubemx/CMakeLists.txt` | CMake 工程入口与源码/包含路径清单 |
|
||||||
|
|
||||||
|
## 已合并或删除的过时资料
|
||||||
|
|
||||||
|
以下文件不再作为长期文档维护:
|
||||||
|
|
||||||
|
| 原文件 | 处理方式 | 原因 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| `项目计划.md` | 删除 | 早期计划仍以 FreeRTOS、socket/netconn 为目标,已与当前 bare-metal + lwIP RAW 实现不一致 |
|
||||||
|
| `uart-ch390-debug-handoff.md` | 删除并将有效结论并入 `工程调试指南.md` | 阶段性调试交接记录,包含旧 AT 命令、旧换行口径和历史测试现场信息 |
|
||||||
|
| `Keil工程配置说明.txt` | 删除并将有效构建入口并入本索引和 `代码结构与阅读指南.md` | 手工配置清单包含旧 FreeRTOS/sys_arch 路径,容易误导当前工程维护 |
|
||||||
|
| `uv4_stdout.txt` | 删除 | 构建输出日志,不属于长期项目文档 |
|
||||||
|
| `MDK-ARM/build_capture.txt` | 删除 | 构建捕获日志,不属于长期项目文档 |
|
||||||
|
| `MDK-ARM/keil-build-viewer-record.txt` | 删除 | 构建查看器记录文件,不属于长期项目文档 |
|
||||||
|
|
||||||
|
## 文档维护原则
|
||||||
|
|
||||||
|
1. 对外协议只在 `AT固件使用手册.md` 中完整展开;其他文档只引用核心约束,避免重复维护。
|
||||||
|
2. 需求和实现统一使用 `MUX / NET / LINK` 三层模型。
|
||||||
|
3. `LINK[idx]` 是内部配置数组模型,`S1/S2/C1/C2` 是 AT 命令中使用的对外角色名。
|
||||||
|
4. 调试现场日志只在仍有长期诊断价值时整理进 `工程调试指南.md` 或 `CH390_最终结论报告.md`,不要直接保留临时 handoff/log 文件。
|
||||||
|
5. 构建结果、IDE 输出、串口抓包原始记录应放入未纳入长期文档的 artifacts/logs 位置,避免污染项目根目录。
|
||||||
Reference in New Issue
Block a user