35 Commits

Author SHA1 Message Date
gaoro-xiao ed1ece23c3 docs: consolidate project documentation 2026-06-10 10:18:35 +08:00
gaoro-xiao 0e59416477 docs: add code structure reading guide 2026-06-10 10:17:53 +08:00
gaoro-xiao 15c2f2776c revert(net): remove ARP reset on link changes 2026-06-10 10:16:25 +08:00
gaoro-xiao 322bed655c fix(net): reset ARP state on link changes 2026-05-15 00:13:04 +08:00
gaoro-xiao e1f4767e9a fix(ch390): deepen reset recovery 2026-05-15 00:06:42 +08:00
gaoro-xiao 9ce1eed850 docs: record v1.1.1 dirty-network recovery design 2026-05-12 03:31:51 +08:00
gaoro-xiao 004057a6fa fix(ch390): filter noisy ingress before pbuf allocation 2026-05-12 03:31:45 +08:00
gaoro-xiao e203db13ca fix(tcp): recover stalled TCP client connects 2026-05-12 03:31:39 +08:00
gaoro-xiao d36f6b4bee docs: record v1.1.0 low-RAM TCP backpressure design 2026-05-08 05:53:10 +08:00
gaoro-xiao 2679db4129 fix(uart): gate TCP forwarding by UART TX capacity 2026-05-08 05:52:58 +08:00
gaoro-xiao 245d98f58e fix(tcp): add low-RAM delayed-ack buffering for TCP bridge 2026-05-08 05:52:45 +08:00
gaoro-xiao 5567c7412d docs: sync baud config guidance
Document the AT+BAUD flow in the debug guide, including U0/U1 data-port mapping and the save/reset behavior required for USART2/USART3 baud changes.
2026-04-28 20:28:49 +08:00
gaoro-xiao fbe76bbdd5 build: ignore local Keil build capture
Keep generated build_capture output out of version control so release tags point at a clean workspace state.
2026-04-27 03:43:16 +08:00
gaoro-xiao b0aa9ffc96 fix(ch390): restore recovery after emergency reset
Re-sync the CH390 MAC and force a visible link recycle so TCP links are rebuilt after reset instead of staying half-recovered.
2026-04-25 01:12:42 +08:00
gaoro-xiao 6fbe09eec9 build: update Keil build record for watchdog changes 2026-04-24 05:49:11 +08:00
gaoro-xiao be80b9dcb1 feat(iwdg): enable LED-driven watchdog refresh 2026-04-24 05:48:54 +08:00
gaoro-xiao 5e9b140db8 feat(at): add UART baud AT commands 2026-04-24 05:48:38 +08:00
gaoro-xiao edfcc0991c build: 忽略本地构建与会话临时文件 2026-04-18 23:43:04 +08:00
gaoro-xiao aceacbdba1 build: 同步Keil工程配置与构建记录 2026-04-18 23:21:31 +08:00
gaoro-xiao b107a3169c docs: 补充MUX丢包修复记录与回归结果 2026-04-18 18:48:57 +08:00
gaoro-xiao 495fbe4298 fix(mux): 修复MUX半帧丢失与发送路径静默失败 2026-04-18 18:48:38 +08:00
gaoro-xiao a0b27d34a0 chore: update Keil project files and build record for V1.0.1 2026-04-14 03:46:58 +08:00
gaoro-xiao 31a3da48fa fix(tcp): MUX模式网口失联 — 对端关闭时用tcp_abort替代tcp_close避免TIME_WAIT耗尽pcb池
根因: tcp_close()将对端关闭的pcb推入TIME_WAIT(120s), 占用MEMP_TCP_PCB池(仅4个),
多连接同时断开后pcb池耗尽, tcp_new()返回NULL, 新连接无法建立直到120s超时释放。

核心修复:
- tcp_server/client: 对端关闭(p=NULL)时tcp_abort替代tcp_close, pcb立即释放
- ch390_runtime: PKT_ERR恢复强制OR上RCR_RXEN(与WCH官方一致)
- ch390_runtime: TX连续超时3次自动emergency reset
- ch390_runtime: 每5秒health_check读VID验证芯片存活
- main: App_StartLinksIfNeeded失败时不标记g_links_started, 允许重试
- main: MUX逐帧RTT printf改为#if DEBUG门控, 减少主循环延迟
- uart_trans: MUX帧解析改为先搜0x7E再消费header, 非法帧只丢1字节
2026-04-14 03:44:26 +08:00
gaoro-xiao efb88ea367 feat(ch390): optimize SPI transfer, MAC fallback, and build settings for V1.0.0
- increase UART DMA/ring buffer sizes for mux traffic
- switch SPI1 to Mode0 with prescaler /2 and align CubeMX settings
- refactor CH390 memory read/write path with chunked SPI read and HAL bulk write
- fallback to hardware MAC when configured MAC is invalid (all-zero)
- add mux frame RTT logs and remove redundant UART1 polling
- update Keil post-build viewer integration and include build viewer artifacts
- update AT manual with all-zero MAC behavior
2026-04-05 03:49:27 +08:00
gaoro-xiao c5b2bdd2d2 feat(AT): LINK 对外接口改为 S1/S2/C1/C2
- LINK 首参数由数字索引改为角色名(S1/S2/C1/C2),内部映射对用户隐藏

- LINK 查询与摘要回包统一输出角色名

- LINK 配置成功后返回当前记录,格式与查询一致

- 同步更新 AT 使用手册中的命令示例与字段说明
2026-04-04 15:44:18 +08:00
gaoro-xiao d5b2506269 feat: save stable CH390 bridge baseline 2026-04-04 02:48:21 +08:00
gaoro-xiao 6f4ba247a4 docs: rewrite mux/net/link protocol manuals 2026-04-03 16:47:15 +08:00
gaoro-xiao ac04bfc923 docs: add tcp bridge debug tool workflow 2026-04-03 06:04:46 +08:00
gaoro-xiao da0f8ef72c build: sync project files and ignore local captures 2026-04-03 05:59:37 +08:00
gaoro-xiao 9fd748c512 fix: harden tcp bridge reconnect handling 2026-04-03 05:57:52 +08:00
gaoro-xiao fd1fae8ad7 fix: restore CH390 bridge flow and sync driver docs 2026-04-03 05:18:02 +08:00
gaoro-xiao 1ef1ba9490 refactor: remove CH390 bitbang read path and sync manuals 2026-04-02 14:36:29 +08:00
gaoro-xiao 81594c6520 docs: record CH390 hardware-bound conclusion 2026-04-01 04:30:27 +08:00
gaoro-xiao 1808f9916f fix: harden CH390 bring-up diagnostics 2026-04-01 04:22:13 +08:00
gaoro-xiao 14a532290d refactor: serialize CH390 runtime SPI access
Move runtime CH390 transactions behind a single ch390_runtime owner so main, lwIP glue, and EXTI no longer compete for SPI access. Keep the system stable under runtime load and capture the remaining CH390 readback failure as a credible low-level device-response issue in the handoff logs.
2026-04-01 03:39:08 +08:00
41 changed files with 6494 additions and 2161 deletions
+12
View File
@@ -28,6 +28,9 @@ Release/
MDK-ARM/DebugConfig/ MDK-ARM/DebugConfig/
MDK-ARM/TCP2UART/ MDK-ARM/TCP2UART/
# CMake build
build/
# OS # OS
Thumbs.db Thumbs.db
Desktop.ini Desktop.ini
@@ -38,3 +41,12 @@ Desktop.ini
# Local debug handoff logs # Local debug handoff logs
.debug/ .debug/
# Local packet captures
WiresharkLog/
# Local build/session artifacts
.embeddedskills/
uv4_stdout.txt
MDK-ARM/EventRecorderStub.scvd
MDK-ARM/build_capture.txt
+456
View File
@@ -0,0 +1,456 @@
# TCP2UART AT 固件使用手册
## 1. 文档范围
本文档定义 `TCP2UART` 项目的最终 AT 外部协议。
本文档只描述最终协议模型,不保留任何历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。
适用对象:
- 上位机开发人员
- 联调与测试人员
- 固件接口实现人员
## 2. 设备与接口
- 主控:`STM32F103R8T6`
- 以太网芯片:`CH390D`
- 配置口:`USART1`
- 数据口:`USART2``USART3`
职责划分:
- `USART1`AT 配置口
- `USART2 / USART3`:业务数据口,可工作于普通透传或 MUX 透传模式
## 3. 最终协议模型
本项目最终控制协议由三部分组成:
1. `MUX`:全局数据承载模式开关
2. `NET`:全局静态网络配置记录
3. `LINK`:按角色名组织的链路配置记录(`S1/S2/C1/C2`
约束如下:
- 设备只有一张网卡,因此本地网络参数只配置一次
- DHCP 不属于最终协议范围
- 所有 AT 文本命令必须以 `\r\n` 结尾
-`DSTMASK=0x00` 时,MUX 数据口中的系统控制帧进入 AT 解析路径,其控制文本同样必须以 `\r\n` 结尾
## 4. MUX 帧格式
`MUX=1` 时,数据口使用如下帧格式:
```text
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
```
字段定义:
- `SYNC`:帧起始标记,建议固定为 `0x7E`
- `LEN_H / LEN_L``PAYLOAD` 长度,高字节在前
- `SRCID`:单字节源端点 ID
- `DSTMASK`:单字节目标端点位图
- `PAYLOAD`:负载数据
- `TAIL`:帧结束标记,建议固定为 `0x7F`
规则:
- `DSTMASK != 0x00`:业务数据帧
- `DSTMASK = 0x00`:系统控制帧
- 系统控制帧的 `PAYLOAD` 为 AT 文本,必须以 `\r\n` 结束
## 5. 统一端点编码
`UART``TCP` 逻辑实例统一进入同一套编码空间:
| 端点 | 编码 |
|------|------|
| `C1` | `0x01` |
| `C2` | `0x02` |
| `UART2` | `0x04` |
| `UART3` | `0x08` |
| `S1` | `0x10` |
| `S2` | `0x20` |
说明:
- `SRCID` 必须为单一端点值
- `DSTMASK` 可以是一个或多个端点编码按位或的结果
- `DSTMASK=0x00` 保留给系统控制帧
## 6. AT 命令总则
### 6.1 命令结尾
所有 AT 命令均必须以 `\r\n` 结尾。
例如:
```text
AT\r\n
AT+MUX?\r\n
AT+NET=192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
```
### 6.2 持久化规则
参数设置成功后只写入当前运行配置,不会自动写入 Flash。
若要掉电保持,必须执行:
1. `AT+SAVE\r\n`
2. `AT+RESET\r\n`
### 6.3 响应风格
- 成功:`OK`
- 需要保存后生效时,允许追加提示文本
- 失败:`ERROR: <reason>`
## 7. 默认配置
### 7.1 MUX 默认值
```text
MUX = 0
```
### 7.2 NET 默认值
```text
NET = 192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01
```
### 7.3 LINK 默认值
```text
LINK:S1 = 1,8080,0.0.0.0,0,U0
LINK:S2 = 0,8081,0.0.0.0,0,U1
LINK:C1 = 1,9001,192.168.1.200,9000,U1
LINK:C2 = 0,9002,192.168.1.201,9001,U0
```
说明:
- `S1/S2/C1/C2` 为对外可见角色名
- 内部索引映射由固件管理,不对外暴露
UART 记号约定:
- `U0 = USART2`
- `U1 = USART3`
### 7.4 BAUD 默认值
```text
BAUD = U0,115200 / U1,115200
```
## 8. AT 命令定义
### 8.1 测试设备在线
命令:
```text
AT\r\n
```
返回:
```text
OK
```
### 8.2 查询摘要
命令:
```text
AT+?\r\n
AT+QUERY\r\n
```
推荐返回格式:
```text
+NET:IP=192.168.1.100,MASK=255.255.255.0,GW=192.168.1.1,MAC=02:00:00:00:00:01
+LINK:S1,EN=1,LPORT=8080,RIP=0.0.0.0,RPORT=0,UART=U0
+LINK:S2,EN=0,LPORT=8081,RIP=0.0.0.0,RPORT=0,UART=U1
+LINK:C1,EN=1,LPORT=9001,RIP=192.168.1.200,RPORT=9000,UART=U1
+LINK:C2,EN=0,LPORT=9002,RIP=192.168.1.201,RPORT=9001,UART=U0
+MUX:0
+MAP:UART2=0x04,UART3=0x08,C1=0x01,C2=0x02,S1=0x10,S2=0x20
+BAUD:U0=115200,U1=115200
OK
```
### 8.3 MUX 类命令
#### 设置 MUX
```text
AT+MUX=1\r\n
```
参数:
- `0`:普通透传模式
- `1`MUX 透传模式
查询:
```text
AT+MUX?\r\n
```
返回示例:
```text
+MUX:1
OK
```
### 8.4 NET 类命令
#### 设置 NET
```text
AT+NET=192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
```
字段顺序:
```text
IP,MASK,GW,MAC
```
查询:
```text
AT+NET?\r\n
```
返回示例:
```text
+NET:IP=192.168.1.100,MASK=255.255.255.0,GW=192.168.1.1,MAC=02:00:00:00:00:01
OK
```
**MAC 设置说明:**
当MAC设置为全0时,固件将使用硬件MAC地址,此时通过AT+?查询到的MAC地址即为当前生效的硬件MAC地址。
### 8.5 BAUD 类命令
#### 查询 UART 波特率
```text
AT+BAUD?\r\n
```
返回示例:
```text
+BAUD:U0=115200,U1=115200
OK
```
#### 设置 UART 波特率
```text
AT+BAUD=U0,115200\r\n
AT+BAUD=U1,38400\r\n
```
字段顺序:
```text
UART,BAUDRATE
```
字段说明:
- `UART``U0/U1`
- `BAUDRATE`:范围 `1200~921600`
说明:
- 该命令只更新当前运行配置记录,不会立即重初始化 `USART2/USART3`
- 执行 `AT+SAVE` 后再执行 `AT+RESET`,重启时按保存值生效
### 8.6 LINK 类命令
#### 设置单条 LINK 记录
```text
AT+LINK=S1,1,8080,0.0.0.0,0,U0\r\n
AT+LINK=C1,1,9001,192.168.1.200,9000,U1\r\n
```
字段顺序:
```text
ROLE,EN,LPORT,RIP,RPORT,UART
```
字段说明:
- `ROLE`:链路角色名,固定为 `S1/S2/C1/C2`
- `EN``0/1`
- `LPORT`:本地端口
- `RIP`:对端 IP
- `RPORT`:对端端口
- `UART``U0/U1`
说明:
- `Server``Client` 共用同一条 `LINK` 记录模型
- `Server``RIP/RPORT` 可作为允许接入的对端约束或预设对端信息
- `Client``RIP/RPORT` 表示远端目标地址与端口
#### 查询单条 LINK
```text
AT+LINK=S1\r\n
```
返回示例:
```text
+LINK:S1,EN=1,LPORT=8080,RIP=0.0.0.0,RPORT=0,UART=U0
OK
```
#### 查询全部 LINK
```text
AT+LINK?\r\n
```
返回示例:
```text
+LINK:S1,EN=1,LPORT=8080,RIP=0.0.0.0,RPORT=0,UART=U0
+LINK:S2,EN=0,LPORT=8081,RIP=0.0.0.0,RPORT=0,UART=U1
+LINK:C1,EN=1,LPORT=9001,RIP=192.168.1.200,RPORT=9000,UART=U1
+LINK:C2,EN=0,LPORT=9002,RIP=192.168.1.201,RPORT=9001,UART=U0
OK
```
## 9. 保存与复位命令
### 9.1 保存配置
```text
AT+SAVE\r\n
```
成功返回:
```text
OK: Configuration saved
```
### 9.2 软件复位
```text
AT+RESET\r\n
```
返回:
```text
OK: Resetting...
```
### 9.3 恢复默认值
```text
AT+DEFAULT\r\n
```
返回:
```text
OK: Defaults restored
```
## 10. 常见错误返回
| 场景 | 返回 |
|------|------|
| 未知命令 | `ERROR: Unknown command` |
| 非法端口 | `ERROR: Invalid port` |
| 非法波特率 | `ERROR: Invalid baudrate` |
| 非法 IP 地址 | `ERROR: Invalid IP format` |
| 非法掩码 | `ERROR: Invalid mask format` |
| 非法网关 | `ERROR: Invalid gateway format` |
| 非法远端 IP | `ERROR: Invalid remote IP format` |
| 非法 MAC | `ERROR: Invalid MAC format` |
| 非法 `SRCID` / `DSTMASK` | `ERROR: Invalid route field` |
| Flash 保存失败 | `ERROR: Save failed` |
## 11. 推荐配置流程
```text
AT+NET=192.168.1.123,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
AT+LINK=S1,1,10001,0.0.0.0,0,U1\r\n
AT+LINK=S2,1,10003,0.0.0.0,0,U1\r\n
AT+LINK=C1,1,20001,192.168.1.201,10002,U0\r\n
AT+MUX=1\r\n
AT+SAVE\r\n
AT+RESET\r\n
```
## 12. 故障排查建议
### 12.1 发送 `AT` 没有返回
优先检查:
1. 是否连接到 `USART1`
2. 串口参数是否为 `115200 8N1`
3. 是否严格使用 `\r\n` 作为命令结尾
4. 接线是否正确
5. 设备是否正常上电运行
### 12.2 设置成功但重启后参数丢失
检查是否漏掉以下步骤:
1. `AT+SAVE\r\n`
2. `AT+RESET\r\n`
### 12.3 MUX 模式数据口有丢包
`MUX=1` 下出现“主机侧已发送,但设备对端收到数量明显偏少”的现象,优先按以下顺序检查:
1. 固件版本是否已经包含 `2026-04-18` 的 MUX 丢包修复。
2. MUX 帧是否完整,尤其是:
- `SYNC=0x7E`
- `LEN_H/LEN_L`
- `SRCID`
- `DSTMASK`
- `TAIL=0x7F`
3. 上位机发送方式是否把一帧拆成多个不连续小片段,或在帧间插入无效字节。
4. TCP 对端是否出现拥塞、窗口缩小或应用层不及时取数,导致发送路径出现背压。
5. RTT 中是否存在链路错误、发送失败或持续重连现象。
当前版本的修复点如下:
1. MUX 解析器改为在整帧完整到齐前不推进 UART RX ring 读指针,避免半帧被破坏性消费。
2. TCP 发送路径与 UART 写入路径不再把背压和短写静默视为成功,便于及早暴露链路承载问题。
现场回归结果:在修复后的固件中,MUX 模式持续发送 `670` 包,接收端 `670` 包全部到达,`0` 丢包。
## 13. 相关文件
- AT 命令实现:[config.c](/D:/code/STM32Project/TCP2UART/App/config.c)
- 配置结构与默认值:[config.h](/D:/code/STM32Project/TCP2UART/App/config.h)
- 调试指导:[工程调试指南.md](/D:/code/STM32Project/TCP2UART/工程调试指南.md)
- 文档索引:[项目文档索引.md](/D:/code/STM32Project/TCP2UART/项目文档索引.md)
+620 -199
View File
File diff suppressed because it is too large Load Diff
+63 -159
View File
@@ -1,96 +1,78 @@
/** /**
* @file config.h * @file config.h
* @brief AT command configuration module for TCP2UART * @brief Final AT configuration model for TCP2UART.
*
* Handles UART1 AT commands for network and serial port configuration.
*
* Supported AT commands:
* - AT+IP=192.168.1.100 Set device IP
* - AT+MASK=255.255.255.0 Set subnet mask
* - AT+GW=192.168.1.1 Set gateway
* - AT+PORT=8080 Set TCP Server listen port
* - AT+RIP=192.168.1.200 Set TCP Client remote IP
* - AT+RPORT=9000 Set TCP Client remote port
* - AT+BAUD1=115200 Set UART2 baudrate
* - AT+BAUD2=115200 Set UART3 baudrate
* - AT+MAC=00:11:22:33:44:55 Set MAC address
* - AT+DHCP=0/1 Enable/disable DHCP
* - AT+SAVE Save parameters to Flash
* - AT+RESET Reset device
* - AT+DEFAULT Restore factory defaults
* - AT+? Query current configuration
*/ */
#ifndef __CONFIG_H__ #ifndef __CONFIG_H__
#define __CONFIG_H__ #define __CONFIG_H__
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* Configuration magic number "TCPU" */ #define CONFIG_MAGIC 0x54435055u
#define CONFIG_MAGIC 0x54435055 #define CONFIG_VERSION 0x0003u
/* Configuration version for compatibility */ #define CONFIG_UART_COUNT 2u
#define CONFIG_VERSION 0x0001 #define CONFIG_LINK_COUNT 4u
#define CONFIG_LINK_S1 0u
#define CONFIG_LINK_S2 1u
#define CONFIG_LINK_C1 2u
#define CONFIG_LINK_C2 3u
#define ENDPOINT_C1 0x01u
#define ENDPOINT_C2 0x02u
#define ENDPOINT_UART2 0x04u
#define ENDPOINT_UART3 0x08u
#define ENDPOINT_S1 0x10u
#define ENDPOINT_S2 0x20u
#define LINK_UART_U0 0u
#define LINK_UART_U1 1u
typedef enum {
MUX_MODE_RAW = 0,
MUX_MODE_FRAME = 1
} mux_mode_t;
/* Device configuration structure */
typedef struct { typedef struct {
uint32_t magic; /* Magic number for validation */ uint8_t ip[4];
uint16_t version; /* Configuration version */ uint8_t mask[4];
uint16_t reserved; /* Reserved for alignment */ uint8_t gw[4];
uint8_t mac[6];
/* Network settings */ uint8_t reserved[2];
uint8_t mac[6]; /* MAC address */ } net_config_t;
uint8_t dhcp_enable; /* DHCP enable flag */
uint8_t reserved2; /* Reserved for alignment */ typedef struct {
uint8_t ip[4]; /* Device IP address */ uint8_t enabled;
uint8_t mask[4]; /* Subnet mask */ uint8_t uart;
uint8_t gw[4]; /* Gateway */ uint16_t local_port;
uint8_t remote_ip[4];
/* TCP Server settings */ uint16_t remote_port;
uint16_t server_port; /* Server listen port */ uint16_t reserved;
uint16_t reserved3; /* Reserved for alignment */ } link_config_t;
/* TCP Client settings */ typedef struct {
uint8_t remote_ip[4]; /* Remote server IP */ uint32_t magic;
uint16_t remote_port; /* Remote server port */ uint16_t version;
uint16_t reconnect_interval;/* Reconnect interval (ms) */ uint8_t mux_mode;
uint8_t reserved0;
/* UART settings */ net_config_t net;
uint32_t uart2_baudrate; /* UART2 (Server) baudrate */ link_config_t links[CONFIG_LINK_COUNT];
uint32_t uart3_baudrate; /* UART3 (Client) baudrate */ uint32_t uart_baudrate[CONFIG_UART_COUNT];
uint8_t uart2_databits; /* UART2 data bits */
uint8_t uart2_stopbits; /* UART2 stop bits */
uint8_t uart2_parity; /* UART2 parity */
uint8_t uart3_databits; /* UART3 data bits */
uint8_t uart3_stopbits; /* UART3 stop bits */
uint8_t uart3_parity; /* UART3 parity */
uint16_t reserved4; /* Reserved for alignment */
/* CRC32 checksum (must be last) */
uint32_t crc; uint32_t crc;
} device_config_t; } device_config_t;
/* Default configuration values */ #define DEFAULT_NET_IP {192, 168, 1, 100}
#define DEFAULT_IP {192, 168, 1, 100} #define DEFAULT_NET_MASK {255, 255, 255, 0}
#define DEFAULT_MASK {255, 255, 255, 0} #define DEFAULT_NET_GW {192, 168, 1, 1}
#define DEFAULT_GW {192, 168, 1, 1} #define DEFAULT_NET_MAC {0x02, 0x00, 0x00, 0x00, 0x00, 0x01}
#define DEFAULT_MAC {0x02, 0x00, 0x00, 0x00, 0x00, 0x01} #define DEFAULT_UART_BAUDRATE 115200u
#define DEFAULT_SERVER_PORT 8080
#define DEFAULT_REMOTE_IP {192, 168, 1, 200}
#define DEFAULT_REMOTE_PORT 9000
#define DEFAULT_UART_BAUDRATE 115200
#define DEFAULT_UART_DATABITS 8
#define DEFAULT_UART_STOPBITS 1
#define DEFAULT_UART_PARITY 0
#define DEFAULT_DHCP_ENABLE 0
#define DEFAULT_RECONNECT_MS 3000
/* AT command result codes */
typedef enum { typedef enum {
AT_OK = 0, AT_OK = 0,
AT_ERROR, AT_ERROR,
@@ -100,108 +82,30 @@ typedef enum {
AT_NEED_REBOOT AT_NEED_REBOOT
} at_result_t; } at_result_t;
/**
* @brief Initialize configuration module
* @return 0 on success, negative on error
*/
int config_init(void); int config_init(void);
/**
* @brief Load configuration from Flash
* @return 0 on success, negative on error (defaults loaded)
*/
int config_load(void); int config_load(void);
/**
* @brief Save configuration to Flash
* @return 0 on success, negative on error
*/
int config_save(void); int config_save(void);
/**
* @brief Reset configuration to factory defaults
*/
void config_set_defaults(void); void config_set_defaults(void);
/**
* @brief Get current configuration
* @return Pointer to current configuration (read-only)
*/
const device_config_t *config_get(void); const device_config_t *config_get(void);
/**
* @brief Get mutable configuration for modification
* @return Pointer to configuration structure
*/
device_config_t *config_get_mutable(void); device_config_t *config_get_mutable(void);
/**
* @brief Process AT command received from UART1
* @param cmd Command string (null-terminated)
* @param response Response buffer
* @param max_len Maximum response length
* @return AT command result code
*/
at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_len); at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_len);
/**
* @brief Poll configuration UART and process pending AT commands
*/
void config_poll(void); void config_poll(void);
/**
* @brief Feed one byte received from the config UART.
* @param byte Received byte.
*/
void config_uart_rx_byte(uint8_t byte); void config_uart_rx_byte(uint8_t byte);
/**
* @brief Try to process one AT command frame from an external UART source.
* @param data Input bytes.
* @param len Input length.
* @return true if the frame was recognized as an AT/config command.
*/
bool config_try_process_frame(const uint8_t *data, uint16_t len); bool config_try_process_frame(const uint8_t *data, uint16_t len);
bool config_build_response_frame(const uint8_t *data,
/** uint16_t len,
* @brief Check whether AT+RESET requested a system reset char *response,
*/ uint16_t max_len,
at_result_t *result);
bool config_is_reset_requested(void); bool config_is_reset_requested(void);
/**
* @brief Clear the pending reset request flag
*/
void config_clear_reset_requested(void); void config_clear_reset_requested(void);
/**
* @brief Format IP address to string
* @param ip IP address bytes
* @param str Output string buffer (min 16 bytes)
*/
void config_ip_to_str(const uint8_t *ip, char *str); void config_ip_to_str(const uint8_t *ip, char *str);
/**
* @brief Parse IP address from string
* @param str IP address string (e.g. "192.168.1.100")
* @param ip Output IP address bytes
* @return 0 on success, negative on error
*/
int config_str_to_ip(const char *str, uint8_t *ip); int config_str_to_ip(const char *str, uint8_t *ip);
/**
* @brief Format MAC address to string
* @param mac MAC address bytes
* @param str Output string buffer (min 18 bytes)
*/
void config_mac_to_str(const uint8_t *mac, char *str); void config_mac_to_str(const uint8_t *mac, char *str);
/**
* @brief Parse MAC address from string
* @param str MAC address string (e.g. "00:11:22:33:44:55")
* @param mac Output MAC address bytes
* @return 0 on success, negative on error
*/
int config_str_to_mac(const char *str, uint8_t *mac); int config_str_to_mac(const char *str, uint8_t *mac);
uint8_t config_link_index_to_endpoint(uint8_t index);
uint8_t config_uart_index_to_endpoint(uint8_t uart_index);
bool config_endpoint_is_single(uint8_t endpoint);
#ifdef __cplusplus #ifdef __cplusplus
} }
+14 -45
View File
@@ -19,41 +19,10 @@
* Private Variables * Private Variables
*---------------------------------------------------------------------------*/ *---------------------------------------------------------------------------*/
/* CRC32 lookup table */
static uint32_t g_crc_table[256];
static bool g_crc_table_initialized = false;
/*--------------------------------------------------------------------------- /*---------------------------------------------------------------------------
* Private Functions * Private Functions
*---------------------------------------------------------------------------*/ *---------------------------------------------------------------------------*/
/**
* @brief Initialize CRC32 lookup table
*/
static void crc32_init_table(void)
{
uint32_t i, j, crc;
for (i = 0; i < 256; i++)
{
crc = i;
for (j = 0; j < 8; j++)
{
if (crc & 1)
{
crc = (crc >> 1) ^ CRC32_POLYNOMIAL;
}
else
{
crc >>= 1;
}
}
g_crc_table[i] = crc;
}
g_crc_table_initialized = true;
}
/** /**
* @brief Unlock Flash for writing * @brief Unlock Flash for writing
*/ */
@@ -105,12 +74,6 @@ static HAL_StatusTypeDef flash_program_halfword(uint32_t addr, uint16_t data)
*/ */
int flash_param_init(void) int flash_param_init(void)
{ {
/* Initialize CRC table */
if (!g_crc_table_initialized)
{
crc32_init_table();
}
return 0; return 0;
} }
@@ -243,16 +206,22 @@ uint32_t flash_param_crc32(const void *data, uint32_t len)
const uint8_t *p = (const uint8_t *)data; const uint8_t *p = (const uint8_t *)data;
uint32_t crc = 0xFFFFFFFF; uint32_t crc = 0xFFFFFFFF;
uint32_t i; uint32_t i;
uint32_t j;
/* Initialize table if needed */
if (!g_crc_table_initialized)
{
crc32_init_table();
}
for (i = 0; i < len; i++) for (i = 0; i < len; i++)
{ {
crc = g_crc_table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8); crc ^= p[i];
for (j = 0; j < 8u; ++j)
{
if ((crc & 1u) != 0u)
{
crc = (crc >> 1) ^ CRC32_POLYNOMIAL;
}
else
{
crc >>= 1;
}
}
} }
return crc ^ 0xFFFFFFFF; return crc ^ 0xFFFFFFFF;
+300 -115
View File
@@ -1,15 +1,14 @@
/** /**
* @file tcp_client.c * @file tcp_client.c
* @brief lwIP RAW TCP client for the UART3 bridge. * @brief Indexed lwIP RAW TCP client manager.
*/ */
#include "tcp_client.h" #include "tcp_client.h"
#include "main.h" #include "../Core/Inc/main.h"
#include "../Drivers/LwIP/src/include/lwip/ip_addr.h"
#include "lwip/ip_addr.h" #include "../Drivers/LwIP/src/include/lwip/pbuf.h"
#include "lwip/pbuf.h" #include "../Drivers/LwIP/src/include/lwip/tcp.h"
#include "lwip/tcp.h"
#include <string.h> #include <string.h>
@@ -18,59 +17,147 @@ 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;
tcp_client_config_t config; uint32_t connect_start_ms;
uint8_t index;
tcp_client_instance_config_t config;
tcp_client_status_t status; tcp_client_status_t status;
} tcp_client_ctx_t; } tcp_client_ctx_t;
static tcp_client_ctx_t g_client; static tcp_client_ctx_t g_clients[TCP_CLIENT_INSTANCE_COUNT];
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size) 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 (p != NULL) {
pbuf_free(p);
}
return ERR_ARG;
}
if (err != ERR_OK) { if (err != ERR_OK) {
if (p != NULL) { if (p != NULL) {
pbuf_free(p); pbuf_free(p);
} }
return err; return err;
} }
if (p == NULL) { if (p == NULL) {
tcp_arg(pcb, NULL); tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL); tcp_recv(pcb, NULL);
tcp_sent(pcb, NULL); tcp_sent(pcb, NULL);
tcp_err(pcb, NULL); tcp_err(pcb, NULL);
if (tcp_close(pcb) != ERR_OK) { 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_OK; 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; ctx->status.errors++;
for (uint16_t i = 0; i < q->len; ++i) { return ERR_MEM;
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_CLIENT_RX_BUFFER_SIZE) == 0u) {
ctx->status.errors++;
break;
}
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;
} }
@@ -78,26 +165,37 @@ static err_t tcp_client_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{ {
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg; tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
(void)pcb; (void)pcb;
ctx->status.tx_bytes += len; if (ctx != NULL) {
ctx->status.tx_bytes += len;
}
return ERR_OK; return ERR_OK;
} }
static void tcp_client_on_err(void *arg, err_t err) static void tcp_client_on_err(void *arg, err_t err)
{ {
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg; tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
(void)err; if (ctx == NULL) {
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;
(void)err;
} }
static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err) static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
{ {
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg; tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
if (ctx == NULL) {
return ERR_ARG;
}
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;
@@ -105,7 +203,9 @@ 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_arg(pcb, ctx); tcp_arg(pcb, ctx);
tcp_recv(pcb, tcp_client_on_recv); tcp_recv(pcb, tcp_client_on_recv);
tcp_sent(pcb, tcp_client_on_sent); tcp_sent(pcb, tcp_client_on_sent);
@@ -113,171 +213,256 @@ static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
return ERR_OK; return ERR_OK;
} }
int tcp_client_init(const tcp_client_config_t *config) int tcp_client_init_all(void)
{ {
memset(&g_client, 0, sizeof(g_client)); memset(g_clients, 0, sizeof(g_clients));
g_client.config.server_ip[0] = 192u; for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
g_client.config.server_ip[1] = 168u; g_clients[i].index = i;
g_client.config.server_ip[2] = 1u; g_clients[i].status.state = TCP_CLIENT_STATE_IDLE;
g_client.config.server_ip[3] = 100u; g_clients[i].config.reconnect_interval_ms = TCP_CLIENT_RECONNECT_DELAY_MS;
g_client.config.server_port = TCP_CLIENT_DEFAULT_PORT; g_clients[i].config.auto_reconnect = true;
g_client.config.auto_reconnect = true;
g_client.config.reconnect_interval_ms = TCP_CLIENT_RECONNECT_DELAY_MS;
g_client.status.state = TCP_CLIENT_STATE_IDLE;
if (config != NULL) {
g_client.config = *config;
} }
return 0; return 0;
} }
int tcp_client_connect(void) int tcp_client_config(uint8_t instance, const tcp_client_instance_config_t *config)
{
if (instance >= TCP_CLIENT_INSTANCE_COUNT || config == NULL) {
return -1;
}
g_clients[instance].config = *config;
return 0;
}
int tcp_client_connect(uint8_t instance)
{ {
struct tcp_pcb *pcb; struct tcp_pcb *pcb;
ip_addr_t remote_addr; ip_addr_t remote_addr;
err_t err; err_t err;
tcp_client_ctx_t *ctx;
if (g_client.pcb != NULL) { if (instance >= TCP_CLIENT_INSTANCE_COUNT) {
return -1;
}
ctx = &g_clients[instance];
if (!ctx->config.enabled) {
return 0;
}
if (ctx->pcb != NULL) {
return 0; return 0;
} }
pcb = tcp_new_ip_type(IPADDR_TYPE_V4); pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
if (pcb == NULL) { if (pcb == NULL) {
g_client.status.errors++; ctx->status.errors++;
ctx->status.state = TCP_CLIENT_STATE_ERROR;
return -1; return -1;
} }
if (ctx->config.local_port != 0u) {
err = tcp_bind(pcb, IP_ANY_TYPE, ctx->config.local_port);
if (err != ERR_OK) {
tcp_abort(pcb);
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
ctx->connect_start_ms = 0u;
ctx->status.errors++;
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
return -1;
}
}
IP_ADDR4(&remote_addr, IP_ADDR4(&remote_addr,
g_client.config.server_ip[0], ctx->config.remote_ip[0],
g_client.config.server_ip[1], ctx->config.remote_ip[1],
g_client.config.server_ip[2], ctx->config.remote_ip[2],
g_client.config.server_ip[3]); ctx->config.remote_ip[3]);
g_client.status.state = TCP_CLIENT_STATE_CONNECTING; ctx->status.state = TCP_CLIENT_STATE_CONNECTING;
tcp_arg(pcb, &g_client); ctx->connect_start_ms = HAL_GetTick();
err = tcp_connect(pcb, &remote_addr, g_client.config.server_port, tcp_client_on_connected); tcp_arg(pcb, ctx);
tcp_err(pcb, tcp_client_on_err);
err = tcp_connect(pcb, &remote_addr, ctx->config.remote_port, tcp_client_on_connected);
if (err != ERR_OK) { if (err != ERR_OK) {
tcp_err(pcb, NULL);
tcp_abort(pcb); tcp_abort(pcb);
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
g_client.status.errors++; ctx->connect_start_ms = 0u;
g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms; ctx->status.errors++;
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
return -1; return -1;
} }
g_client.pcb = pcb; ctx->pcb = pcb;
return 0; return 0;
} }
int tcp_client_disconnect(void) int tcp_client_disconnect(uint8_t instance)
{ {
if (g_client.pcb != NULL) { tcp_client_ctx_t *ctx;
tcp_arg(g_client.pcb, NULL);
tcp_recv(g_client.pcb, NULL);
tcp_sent(g_client.pcb, NULL);
tcp_err(g_client.pcb, NULL);
tcp_abort(g_client.pcb);
g_client.pcb = NULL;
}
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; if (instance >= TCP_CLIENT_INSTANCE_COUNT) {
return -1;
}
ctx = &g_clients[instance];
if (ctx->pcb != NULL) {
tcp_client_reset_rx_state(ctx);
tcp_arg(ctx->pcb, NULL);
tcp_recv(ctx->pcb, NULL);
tcp_sent(ctx->pcb, NULL);
tcp_err(ctx->pcb, NULL);
tcp_abort(ctx->pcb);
ctx->pcb = NULL;
}
ctx->connect_start_ms = 0u;
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
tcp_client_reset_rx_state(ctx);
return 0; return 0;
} }
int tcp_client_send(const uint8_t *data, uint16_t len) int tcp_client_send(uint8_t instance, const uint8_t *data, uint16_t len)
{ {
err_t err; err_t err;
tcp_client_ctx_t *ctx;
if (g_client.pcb == NULL || data == NULL || len == 0u) { if (instance >= TCP_CLIENT_INSTANCE_COUNT || data == NULL || len == 0u) {
return -1; return -1;
} }
ctx = &g_clients[instance];
if (tcp_sndbuf(g_client.pcb) < len) { if (ctx->pcb == NULL) {
return -1;
}
if (tcp_sndbuf(ctx->pcb) < len) {
ctx->status.errors++;
return 0;
}
err = tcp_write(ctx->pcb, data, len, TCP_WRITE_FLAG_COPY);
if (err == ERR_MEM) {
ctx->status.errors++;
return 0; return 0;
} }
err = tcp_write(g_client.pcb, data, len, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK) { if (err != ERR_OK) {
g_client.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
err = tcp_output(ctx->pcb);
err = tcp_output(g_client.pcb); if (err == ERR_MEM) {
ctx->status.errors++;
return 0;
}
if (err != ERR_OK) { if (err != ERR_OK) {
g_client.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
return (int)len; return (int)len;
} }
int tcp_client_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms) int tcp_client_recv(uint8_t instance, uint8_t *data, uint16_t max_len)
{ {
uint16_t copied = 0u; uint16_t copied = 0u;
(void)timeout_ms; tcp_client_ctx_t *ctx;
if (data == NULL || max_len == 0u) { if (instance >= TCP_CLIENT_INSTANCE_COUNT || data == NULL || max_len == 0u) {
return -1; return -1;
} }
ctx = &g_clients[instance];
while (copied < max_len && g_client.rx_tail != g_client.rx_head) { tcp_client_fill_ring_from_pbuf(ctx);
data[copied++] = g_client.rx_ring[g_client.rx_tail]; while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
g_client.rx_tail = (uint16_t)((g_client.rx_tail + 1u) % TCP_CLIENT_RX_BUFFER_SIZE); data[copied++] = ctx->rx_ring[ctx->rx_tail];
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;
} }
bool tcp_client_is_connected(void) uint16_t tcp_client_rx_available(uint8_t instance)
{ {
return (g_client.pcb != NULL) && (g_client.status.state == TCP_CLIENT_STATE_CONNECTED); 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);
} }
int tcp_client_set_server(const uint8_t *ip, uint16_t port) uint16_t tcp_client_peek(uint8_t instance, uint8_t *data, uint16_t max_len)
{ {
if (ip == NULL || port == 0u) { uint16_t copied = 0u;
return -1; uint16_t tail;
tcp_client_ctx_t *ctx;
if (instance >= TCP_CLIENT_INSTANCE_COUNT || data == NULL || max_len == 0u) {
return 0u;
} }
memcpy(g_client.config.server_ip, ip, 4u); ctx = &g_clients[instance];
g_client.config.server_port = port; tcp_client_fill_ring_from_pbuf(ctx);
return 0; 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_get_status(tcp_client_status_t *status) void tcp_client_drop(uint8_t instance, uint16_t len)
{ {
if (status != NULL) { tcp_client_ctx_t *ctx;
*status = g_client.status; 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);
} }
} }
void *tcp_client_get_rx_stream(void) bool tcp_client_is_connected(uint8_t instance)
{ {
return NULL; return (instance < TCP_CLIENT_INSTANCE_COUNT) &&
(g_clients[instance].pcb != NULL) &&
(g_clients[instance].status.state == TCP_CLIENT_STATE_CONNECTED);
} }
void *tcp_client_get_tx_stream(void) void tcp_client_get_status(uint8_t instance, tcp_client_status_t *status)
{ {
return NULL; if (instance < TCP_CLIENT_INSTANCE_COUNT && status != NULL) {
} *status = g_clients[instance].status;
}
void tcp_client_task(void *argument)
{
(void)argument;
} }
void tcp_client_poll(void) void tcp_client_poll(void)
{ {
uint32_t now; uint32_t now = HAL_GetTick();
if (!g_client.config.auto_reconnect || g_client.pcb != NULL) { for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
return; 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)) {
now = HAL_GetTick(); continue;
if (now >= g_client.next_retry_ms) { }
g_client.status.reconnect_count++; if ((ctx->pcb != NULL) && (ctx->status.state == TCP_CLIENT_STATE_CONNECTING)) {
g_client.next_retry_ms = now + g_client.config.reconnect_interval_ms; if ((uint32_t)(now - ctx->connect_start_ms) >= TCP_CLIENT_CONNECT_TIMEOUT_MS) {
(void)tcp_client_connect(); tcp_client_abort_connect_timeout(ctx, now);
}
continue;
}
if (tick_reached(now, ctx->next_retry_ms)) {
ctx->status.reconnect_count++;
ctx->next_retry_ms = now + ctx->config.reconnect_interval_ms;
(void)tcp_client_connect(i);
}
} }
} }
+26 -96
View File
@@ -1,130 +1,60 @@
/** /**
* @file tcp_client.h * @file tcp_client.h
* @brief TCP Client module for transparent transmission with UART3 * @brief Indexed lwIP RAW TCP client manager.
*/ */
#ifndef __TCP_CLIENT_H__ #ifndef __TCP_CLIENT_H__
#define __TCP_CLIENT_H__ #define __TCP_CLIENT_H__
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* Default TCP Client settings */ #define TCP_CLIENT_INSTANCE_COUNT 2u
#define TCP_CLIENT_DEFAULT_PORT 8081 #define TCP_CLIENT_RX_BUFFER_SIZE 480u
#define TCP_CLIENT_DEFAULT_SERVER "192.168.1.100" #define TCP_CLIENT_RECONNECT_DELAY_MS 3000u
#define TCP_CLIENT_CONNECT_TIMEOUT_MS 10000u
/* Reconnect settings */
#define TCP_CLIENT_RECONNECT_DELAY_MS 3000
#define TCP_CLIENT_MAX_RECONNECT_TRIES 0 /* 0 = infinite */
/* Buffer sizes */
#define TCP_CLIENT_RX_BUFFER_SIZE 512
#define TCP_CLIENT_TX_BUFFER_SIZE 512
/* TCP Client state */
typedef enum { typedef enum {
TCP_CLIENT_STATE_IDLE, TCP_CLIENT_STATE_IDLE = 0,
TCP_CLIENT_STATE_CONNECTING, TCP_CLIENT_STATE_CONNECTING,
TCP_CLIENT_STATE_CONNECTED, TCP_CLIENT_STATE_CONNECTED,
TCP_CLIENT_STATE_DISCONNECTED, TCP_CLIENT_STATE_DISCONNECTED,
TCP_CLIENT_STATE_ERROR TCP_CLIENT_STATE_ERROR
} tcp_client_state_t; } tcp_client_state_t;
/* TCP Client configuration */
typedef struct { typedef struct {
uint8_t server_ip[4]; /* Server IP address */ uint8_t remote_ip[4];
uint16_t server_port; /* Server port */ uint16_t local_port;
bool auto_reconnect; /* Auto reconnect on disconnect */ uint16_t remote_port;
uint16_t reconnect_interval_ms; /* Reconnect interval */ uint16_t reconnect_interval_ms;
} tcp_client_config_t; bool enabled;
bool auto_reconnect;
} tcp_client_instance_config_t;
/* TCP Client status */
typedef struct { typedef struct {
tcp_client_state_t state; tcp_client_state_t state;
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;
/** int tcp_client_init_all(void);
* @brief Initialize TCP Client module int tcp_client_config(uint8_t instance, const tcp_client_instance_config_t *config);
* @param config Client configuration int tcp_client_connect(uint8_t instance);
* @return 0 on success, negative on error int tcp_client_disconnect(uint8_t instance);
*/ int tcp_client_send(uint8_t instance, const uint8_t *data, uint16_t len);
int tcp_client_init(const tcp_client_config_t *config); 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);
* @brief Connect to remote server void tcp_client_drop(uint8_t instance, uint16_t len);
* @return 0 on success, negative on error bool tcp_client_is_connected(uint8_t instance);
*/ void tcp_client_get_status(uint8_t instance, tcp_client_status_t *status);
int tcp_client_connect(void);
/**
* @brief Disconnect from server
* @return 0 on success, negative on error
*/
int tcp_client_disconnect(void);
/**
* @brief Send data to server
* @param data Data buffer
* @param len Data length
* @return Number of bytes sent, negative on error
*/
int tcp_client_send(const uint8_t *data, uint16_t len);
/**
* @brief Receive data from server
* @param data Data buffer
* @param max_len Maximum length to receive
* @param timeout_ms Timeout in milliseconds (0 = non-blocking)
* @return Number of bytes received, 0 if no data, negative on error
*/
int tcp_client_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms);
/**
* @brief Check if connected to server
* @return true if connected
*/
bool tcp_client_is_connected(void);
/**
* @brief Update server configuration (for AT command)
* @param ip Server IP address (4 bytes)
* @param port Server port
* @return 0 on success, negative on error
*/
int tcp_client_set_server(const uint8_t *ip, uint16_t port);
/**
* @brief Get TCP Client status
* @param status Pointer to status structure
*/
void tcp_client_get_status(tcp_client_status_t *status);
/**
* @brief Get TCP Client RX StreamBuffer handle for UART integration
* @return StreamBuffer handle for receiving data from TCP
*/
void *tcp_client_get_rx_stream(void);
/**
* @brief Get TCP Client TX StreamBuffer handle for UART integration
* @return StreamBuffer handle for sending data to TCP
*/
void *tcp_client_get_tx_stream(void);
/**
* @brief TCP Client task function (for FreeRTOS)
* @param argument Task argument (unused)
*/
void tcp_client_task(void *argument);
void tcp_client_poll(void); void tcp_client_poll(void);
#ifdef __cplusplus #ifdef __cplusplus
+244 -102
View File
@@ -1,12 +1,14 @@
/** /**
* @file tcp_server.c * @file tcp_server.c
* @brief lwIP RAW TCP server for the UART2 bridge. * @brief Indexed lwIP RAW TCP server manager.
*/ */
#include "tcp_server.h" #include "tcp_server.h"
#include "lwip/pbuf.h" #include "../Drivers/LwIP/src/include/lwip/pbuf.h"
#include "lwip/tcp.h" #include "../Drivers/LwIP/src/include/lwip/tcp.h"
#include "SEGGER_RTT.h"
#include <string.h> #include <string.h>
@@ -16,57 +18,113 @@ 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;
tcp_server_config_t config; struct pbuf *hold_pbuf;
uint16_t hold_offset;
uint8_t index;
tcp_server_instance_config_t config;
tcp_server_status_t status; tcp_server_status_t status;
} tcp_server_ctx_t; } tcp_server_ctx_t;
static tcp_server_ctx_t g_server; static tcp_server_ctx_t g_servers[TCP_SERVER_INSTANCE_COUNT];
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size) 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 (p != NULL) {
pbuf_free(p);
}
return ERR_ARG;
}
if (err != ERR_OK) { if (err != ERR_OK) {
if (p != NULL) { if (p != NULL) {
pbuf_free(p); pbuf_free(p);
} }
return err; return err;
} }
if (p == NULL) { if (p == NULL) {
tcp_arg(pcb, NULL); tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL); tcp_recv(pcb, NULL);
tcp_sent(pcb, NULL); tcp_sent(pcb, NULL);
tcp_err(pcb, NULL); tcp_err(pcb, NULL);
if (tcp_close(pcb) != ERR_OK) { tcp_abort(pcb);
tcp_abort(pcb);
}
ctx->client_pcb = NULL; ctx->client_pcb = NULL;
ctx->status.state = TCP_SERVER_STATE_LISTENING; ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE;
return ERR_OK; 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; ctx->status.errors++;
for (uint16_t i = 0; i < q->len; ++i) { return ERR_MEM;
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_SERVER_RX_BUFFER_SIZE) == 0u) {
ctx->status.errors++;
break;
}
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;
} }
@@ -74,27 +132,32 @@ static err_t tcp_server_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{ {
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg; tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
(void)pcb; (void)pcb;
ctx->status.tx_bytes += len; if (ctx != NULL) {
ctx->status.tx_bytes += len;
}
return ERR_OK; return ERR_OK;
} }
static void tcp_server_on_err(void *arg, err_t err) static void tcp_server_on_err(void *arg, err_t err)
{ {
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg; tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
(void)err; if (ctx == NULL) {
return;
}
tcp_server_reset_rx_state(ctx);
ctx->client_pcb = NULL; ctx->client_pcb = NULL;
ctx->status.state = TCP_SERVER_STATE_LISTENING; ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE;
ctx->status.errors++; ctx->status.errors++;
SEGGER_RTT_printf(0, "TCP server[%u] connection error=%d\r\n", ctx->index, (int)err);
} }
static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err) static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{ {
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg; tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
if (err != ERR_OK) { if (ctx == NULL || err != ERR_OK) {
return err; return (ctx == NULL) ? ERR_ARG : err;
} }
if (ctx->client_pcb != NULL) { if (ctx->client_pcb != NULL) {
tcp_abort(newpcb); tcp_abort(newpcb);
return ERR_ABRT; return ERR_ABRT;
@@ -103,7 +166,7 @@ static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
ctx->client_pcb = newpcb; ctx->client_pcb = newpcb;
ctx->status.state = TCP_SERVER_STATE_CONNECTED; ctx->status.state = TCP_SERVER_STATE_CONNECTED;
ctx->status.connections++; ctx->status.connections++;
tcp_nagle_disable(newpcb);
tcp_arg(newpcb, ctx); tcp_arg(newpcb, ctx);
tcp_recv(newpcb, tcp_server_on_recv); tcp_recv(newpcb, tcp_server_on_recv);
tcp_sent(newpcb, tcp_server_on_sent); tcp_sent(newpcb, tcp_server_on_sent);
@@ -111,143 +174,222 @@ static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
return ERR_OK; return ERR_OK;
} }
int tcp_server_init(const tcp_server_config_t *config) int tcp_server_init_all(void)
{ {
memset(&g_server, 0, sizeof(g_server)); memset(g_servers, 0, sizeof(g_servers));
g_server.config.port = TCP_SERVER_DEFAULT_PORT; for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
g_server.config.auto_reconnect = true; g_servers[i].index = i;
g_server.status.state = TCP_SERVER_STATE_IDLE; g_servers[i].status.state = TCP_SERVER_STATE_IDLE;
if (config != NULL) {
g_server.config = *config;
} }
return 0; return 0;
} }
int tcp_server_start(void) int tcp_server_config(uint8_t instance, const tcp_server_instance_config_t *config)
{
if (instance >= TCP_SERVER_INSTANCE_COUNT || config == NULL) {
return -1;
}
g_servers[instance].config = *config;
return 0;
}
int tcp_server_start(uint8_t instance)
{ {
struct tcp_pcb *pcb; struct tcp_pcb *pcb;
err_t err; err_t err;
tcp_server_ctx_t *ctx;
if (g_server.listen_pcb != NULL) { if (instance >= TCP_SERVER_INSTANCE_COUNT) {
return -1;
}
ctx = &g_servers[instance];
if (!ctx->config.enabled) {
ctx->status.state = TCP_SERVER_STATE_IDLE;
return 0;
}
if (ctx->listen_pcb != NULL) {
return 0; return 0;
} }
pcb = tcp_new_ip_type(IPADDR_TYPE_V4); pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
if (pcb == NULL) { if (pcb == NULL) {
g_server.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
err = tcp_bind(pcb, IP_ANY_TYPE, g_server.config.port); err = tcp_bind(pcb, IP_ANY_TYPE, ctx->config.port);
if (err != ERR_OK) { if (err != ERR_OK) {
tcp_abort(pcb); tcp_abort(pcb);
g_server.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
g_server.listen_pcb = tcp_listen_with_backlog(pcb, 1); ctx->listen_pcb = tcp_listen_with_backlog(pcb, 1);
if (g_server.listen_pcb == NULL) { if (ctx->listen_pcb == NULL) {
g_server.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
tcp_arg(g_server.listen_pcb, &g_server); tcp_arg(ctx->listen_pcb, ctx);
tcp_accept(g_server.listen_pcb, tcp_server_on_accept); tcp_accept(ctx->listen_pcb, tcp_server_on_accept);
g_server.status.state = TCP_SERVER_STATE_LISTENING; ctx->status.state = TCP_SERVER_STATE_LISTENING;
return 0; return 0;
} }
int tcp_server_stop(void) int tcp_server_stop(uint8_t instance)
{ {
if (g_server.client_pcb != NULL) { tcp_server_ctx_t *ctx;
tcp_arg(g_server.client_pcb, NULL);
tcp_recv(g_server.client_pcb, NULL); if (instance >= TCP_SERVER_INSTANCE_COUNT) {
tcp_sent(g_server.client_pcb, NULL); return -1;
tcp_err(g_server.client_pcb, NULL); }
tcp_abort(g_server.client_pcb); ctx = &g_servers[instance];
g_server.client_pcb = NULL;
if (ctx->client_pcb != NULL) {
tcp_server_reset_rx_state(ctx);
tcp_arg(ctx->client_pcb, NULL);
tcp_recv(ctx->client_pcb, NULL);
tcp_sent(ctx->client_pcb, NULL);
tcp_err(ctx->client_pcb, NULL);
tcp_abort(ctx->client_pcb);
ctx->client_pcb = NULL;
}
if (ctx->listen_pcb != NULL) {
tcp_arg(ctx->listen_pcb, NULL);
tcp_accept(ctx->listen_pcb, NULL);
if (tcp_close(ctx->listen_pcb) != ERR_OK) {
tcp_abort(ctx->listen_pcb);
}
ctx->listen_pcb = NULL;
} }
if (g_server.listen_pcb != NULL) { ctx->status.state = TCP_SERVER_STATE_IDLE;
tcp_arg(g_server.listen_pcb, NULL); tcp_server_reset_rx_state(ctx);
tcp_accept(g_server.listen_pcb, NULL);
tcp_close(g_server.listen_pcb);
g_server.listen_pcb = NULL;
}
g_server.status.state = TCP_SERVER_STATE_IDLE;
return 0; return 0;
} }
int tcp_server_send(const uint8_t *data, uint16_t len) int tcp_server_send(uint8_t instance, const uint8_t *data, uint16_t len)
{ {
err_t err; err_t err;
tcp_server_ctx_t *ctx;
if (g_server.client_pcb == NULL || data == NULL || len == 0u) { if (instance >= TCP_SERVER_INSTANCE_COUNT || data == NULL || len == 0u) {
return -1; return -1;
} }
ctx = &g_servers[instance];
if (tcp_sndbuf(g_server.client_pcb) < len) { if (ctx->client_pcb == NULL) {
return -1;
}
if (tcp_sndbuf(ctx->client_pcb) < len) {
ctx->status.errors++;
return 0; return 0;
} }
err = tcp_write(g_server.client_pcb, data, len, TCP_WRITE_FLAG_COPY); err = tcp_write(ctx->client_pcb, data, len, TCP_WRITE_FLAG_COPY);
if (err == ERR_MEM) {
ctx->status.errors++;
return 0;
}
if (err != ERR_OK) { if (err != ERR_OK) {
g_server.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
err = tcp_output(ctx->client_pcb);
err = tcp_output(g_server.client_pcb); if (err == ERR_MEM) {
ctx->status.errors++;
return 0;
}
if (err != ERR_OK) { if (err != ERR_OK) {
g_server.status.errors++; ctx->status.errors++;
return -1; return -1;
} }
return (int)len; return (int)len;
} }
int tcp_server_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms) int tcp_server_recv(uint8_t instance, uint8_t *data, uint16_t max_len)
{ {
uint16_t copied = 0u; uint16_t copied = 0u;
(void)timeout_ms; tcp_server_ctx_t *ctx;
if (data == NULL || max_len == 0u) { if (instance >= TCP_SERVER_INSTANCE_COUNT || data == NULL || max_len == 0u) {
return -1; return -1;
} }
ctx = &g_servers[instance];
while (copied < max_len && g_server.rx_tail != g_server.rx_head) { tcp_server_fill_ring_from_pbuf(ctx);
data[copied++] = g_server.rx_ring[g_server.rx_tail]; while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
g_server.rx_tail = (uint16_t)((g_server.rx_tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE); data[copied++] = ctx->rx_ring[ctx->rx_tail];
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;
} }
bool tcp_server_is_connected(void) uint16_t tcp_server_rx_available(uint8_t instance)
{ {
return g_server.client_pcb != NULL; 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);
} }
void tcp_server_get_status(tcp_server_status_t *status) uint16_t tcp_server_peek(uint8_t instance, uint8_t *data, uint16_t max_len)
{ {
if (status != NULL) { uint16_t copied = 0u;
*status = g_server.status; 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)
{
return (instance < TCP_SERVER_INSTANCE_COUNT) && (g_servers[instance].client_pcb != NULL);
}
void tcp_server_get_status(uint8_t instance, tcp_server_status_t *status)
{
if (instance < TCP_SERVER_INSTANCE_COUNT && status != NULL) {
*status = g_servers[instance].status;
} }
} }
void *tcp_server_get_rx_stream(void) void tcp_server_poll(void)
{ {
return NULL; for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
} tcp_server_fill_ring_from_pbuf(&g_servers[i]);
}
void *tcp_server_get_tx_stream(void)
{
return NULL;
}
void tcp_server_task(void *argument)
{
(void)argument;
} }
+19 -82
View File
@@ -1,43 +1,33 @@
/** /**
* @file tcp_server.h * @file tcp_server.h
* @brief TCP Server module for transparent transmission with UART2 * @brief Indexed lwIP RAW TCP server manager.
*/ */
#ifndef __TCP_SERVER_H__ #ifndef __TCP_SERVER_H__
#define __TCP_SERVER_H__ #define __TCP_SERVER_H__
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* Default TCP Server port */ #define TCP_SERVER_INSTANCE_COUNT 2u
#define TCP_SERVER_DEFAULT_PORT 8080 #define TCP_SERVER_RX_BUFFER_SIZE 480u
/* Maximum number of simultaneous connections */
#define TCP_SERVER_MAX_CONNECTIONS 1
/* Buffer sizes */
#define TCP_SERVER_RX_BUFFER_SIZE 512
#define TCP_SERVER_TX_BUFFER_SIZE 512
/* TCP Server state */
typedef enum { typedef enum {
TCP_SERVER_STATE_IDLE, TCP_SERVER_STATE_IDLE = 0,
TCP_SERVER_STATE_LISTENING, TCP_SERVER_STATE_LISTENING,
TCP_SERVER_STATE_CONNECTED, TCP_SERVER_STATE_CONNECTED,
TCP_SERVER_STATE_ERROR TCP_SERVER_STATE_ERROR
} tcp_server_state_t; } tcp_server_state_t;
/* TCP Server configuration */
typedef struct { typedef struct {
uint16_t port; uint16_t port;
bool auto_reconnect; bool enabled;
} tcp_server_config_t; } tcp_server_instance_config_t;
/* TCP Server status */
typedef struct { typedef struct {
tcp_server_state_t state; tcp_server_state_t state;
uint32_t rx_bytes; uint32_t rx_bytes;
@@ -46,71 +36,18 @@ typedef struct {
uint32_t errors; uint32_t errors;
} tcp_server_status_t; } tcp_server_status_t;
/** int tcp_server_init_all(void);
* @brief Initialize TCP Server module int tcp_server_config(uint8_t instance, const tcp_server_instance_config_t *config);
* @param config Server configuration int tcp_server_start(uint8_t instance);
* @return 0 on success, negative on error int tcp_server_stop(uint8_t instance);
*/ int tcp_server_send(uint8_t instance, const uint8_t *data, uint16_t len);
int tcp_server_init(const tcp_server_config_t *config); 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);
* @brief Start TCP Server (begin listening) void tcp_server_drop(uint8_t instance, uint16_t len);
* @return 0 on success, negative on error bool tcp_server_is_connected(uint8_t instance);
*/ void tcp_server_get_status(uint8_t instance, tcp_server_status_t *status);
int tcp_server_start(void); void tcp_server_poll(void);
/**
* @brief Stop TCP Server
* @return 0 on success, negative on error
*/
int tcp_server_stop(void);
/**
* @brief Send data to connected client
* @param data Data buffer
* @param len Data length
* @return Number of bytes sent, negative on error
*/
int tcp_server_send(const uint8_t *data, uint16_t len);
/**
* @brief Receive data from connected client
* @param data Data buffer
* @param max_len Maximum length to receive
* @param timeout_ms Timeout in milliseconds (0 = non-blocking)
* @return Number of bytes received, 0 if no data, negative on error
*/
int tcp_server_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms);
/**
* @brief Check if client is connected
* @return true if connected
*/
bool tcp_server_is_connected(void);
/**
* @brief Get TCP Server status
* @param status Pointer to status structure
*/
void tcp_server_get_status(tcp_server_status_t *status);
/**
* @brief Get TCP Server StreamBuffer handle for UART integration
* @return StreamBuffer handle for receiving data from TCP
*/
void *tcp_server_get_rx_stream(void);
/**
* @brief Get TCP Server TX StreamBuffer handle for UART integration
* @return StreamBuffer handle for sending data to TCP
*/
void *tcp_server_get_tx_stream(void);
/**
* @brief TCP Server task function (for FreeRTOS)
* @param argument Task argument (unused)
*/
void tcp_server_task(void *argument);
#ifdef __cplusplus #ifdef __cplusplus
} }
+200 -79
View File
@@ -1,14 +1,17 @@
/** /**
* @file uart_trans.c * @file uart_trans.c
* @brief Bare-metal UART DMA/IDLE transport layer. * @brief Bare-metal UART DMA/IDLE transport and MUX helpers.
*/ */
#include "uart_trans.h" #include "uart_trans.h"
#include "usart.h" #include "../Core/Inc/usart.h"
#include <string.h> #include <string.h>
#define UART_MUX_SYNC 0x7Eu
#define UART_MUX_TAIL 0x7Fu
typedef struct { typedef struct {
UART_HandleTypeDef *huart; UART_HandleTypeDef *huart;
uint8_t rx_dma_buffer[UART_RX_DMA_BUFFER_SIZE]; uint8_t rx_dma_buffer[UART_RX_DMA_BUFFER_SIZE];
@@ -40,55 +43,69 @@ static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
return (uint16_t)(size - ring_used(head, tail, size) - 1u); return (uint16_t)(size - ring_used(head, tail, size) - 1u);
} }
static void apply_default_config(uart_channel_ctx_t *ctx) static bool ring_peek_byte(const uart_channel_ctx_t *ctx, uint16_t offset, uint8_t *out)
{ {
ctx->config.baudrate = UART_DEFAULT_BAUDRATE; uint16_t head;
ctx->config.data_bits = UART_DEFAULT_DATA_BITS; uint16_t tail;
ctx->config.stop_bits = UART_DEFAULT_STOP_BITS;
ctx->config.parity = UART_DEFAULT_PARITY; if (ctx == NULL || out == NULL) {
return false;
}
head = ctx->rx_head;
tail = ctx->rx_tail;
if (offset >= ring_used(head, tail, UART_RX_RING_BUFFER_SIZE)) {
return false;
}
*out = ctx->rx_ring[(tail + offset) % UART_RX_RING_BUFFER_SIZE];
return true;
}
static bool ring_peek_span(const uart_channel_ctx_t *ctx, uint16_t offset, uint8_t *data, uint16_t len)
{
if (ctx == NULL || data == NULL) {
return false;
}
for (uint16_t i = 0u; i < len; ++i) {
if (!ring_peek_byte(ctx, (uint16_t)(offset + i), &data[i])) {
return false;
}
}
return true;
}
static void ring_drop_bytes(uart_channel_ctx_t *ctx, uint16_t len)
{
if (ctx == NULL) {
return;
}
while (len > 0u && ctx->rx_tail != ctx->rx_head) {
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % UART_RX_RING_BUFFER_SIZE);
--len;
}
} }
static int apply_uart_config(uart_channel_t channel) static int apply_uart_config(uart_channel_t channel)
{ {
uart_channel_ctx_t *ctx = &g_channels[channel]; uart_channel_ctx_t *ctx = &g_channels[channel];
UART_HandleTypeDef *huart = ctx->huart; if (ctx->huart == NULL) {
uint32_t word_length;
uint32_t parity;
if (huart == NULL) {
return -1; return -1;
} }
if (ctx->running) { if (ctx->running) {
HAL_UART_DMAStop(huart); HAL_UART_DMAStop(ctx->huart);
ctx->running = false; ctx->running = false;
} }
huart->Init.BaudRate = ctx->config.baudrate; ctx->huart->Init.BaudRate = ctx->config.baudrate;
huart->Init.StopBits = (ctx->config.stop_bits == 2u) ? UART_STOPBITS_2 : UART_STOPBITS_1; ctx->huart->Init.WordLength = UART_WORDLENGTH_8B;
ctx->huart->Init.StopBits = UART_STOPBITS_1;
switch (ctx->config.parity) { ctx->huart->Init.Parity = UART_PARITY_NONE;
case 1: return (HAL_UART_Init(ctx->huart) == HAL_OK) ? 0 : -1;
parity = UART_PARITY_ODD;
break;
case 2:
parity = UART_PARITY_EVEN;
break;
default:
parity = UART_PARITY_NONE;
break;
}
if (parity == UART_PARITY_NONE) {
word_length = (ctx->config.data_bits == 9u) ? UART_WORDLENGTH_9B : UART_WORDLENGTH_8B;
} else {
word_length = (ctx->config.data_bits >= 8u) ? UART_WORDLENGTH_9B : UART_WORDLENGTH_8B;
}
huart->Init.WordLength = word_length;
huart->Init.Parity = parity;
return (HAL_UART_Init(huart) == HAL_OK) ? 0 : -1;
} }
static void process_rx_snapshot(uart_channel_t channel, uint16_t dma_write_index) static void process_rx_snapshot(uart_channel_t channel, uint16_t dma_write_index)
@@ -96,9 +113,7 @@ static void process_rx_snapshot(uart_channel_t channel, uint16_t dma_write_index
uart_channel_ctx_t *ctx = &g_channels[channel]; uart_channel_ctx_t *ctx = &g_channels[channel];
while (ctx->rx_dma_read_index != dma_write_index) { while (ctx->rx_dma_read_index != dma_write_index) {
uint16_t next_head; uint16_t next_head = (uint16_t)((ctx->rx_head + 1u) % UART_RX_RING_BUFFER_SIZE);
next_head = (uint16_t)((ctx->rx_head + 1u) % UART_RX_RING_BUFFER_SIZE);
if (next_head == ctx->rx_tail) { if (next_head == ctx->rx_tail) {
ctx->stats.errors++; ctx->stats.errors++;
break; break;
@@ -149,16 +164,12 @@ static void kick_tx(uart_channel_t channel)
int uart_trans_init(void) int uart_trans_init(void)
{ {
memset(g_channels, 0, sizeof(g_channels)); memset(g_channels, 0, sizeof(g_channels));
g_channels[UART_CHANNEL_U0].huart = &huart2;
g_channels[UART_CHANNEL_SERVER].huart = &huart2; g_channels[UART_CHANNEL_U1].huart = &huart3;
g_channels[UART_CHANNEL_CLIENT].huart = &huart3; g_channels[UART_CHANNEL_U0].config.baudrate = UART_DEFAULT_BAUDRATE;
g_channels[UART_CHANNEL_U1].config.baudrate = UART_DEFAULT_BAUDRATE;
apply_default_config(&g_channels[UART_CHANNEL_SERVER]); g_channels[UART_CHANNEL_U0].initialized = true;
apply_default_config(&g_channels[UART_CHANNEL_CLIENT]); g_channels[UART_CHANNEL_U1].initialized = true;
g_channels[UART_CHANNEL_SERVER].initialized = true;
g_channels[UART_CHANNEL_CLIENT].initialized = true;
return 0; return 0;
} }
@@ -167,7 +178,6 @@ int uart_trans_config(uart_channel_t channel, const uart_config_t *config)
if (channel >= UART_CHANNEL_MAX || config == NULL) { if (channel >= UART_CHANNEL_MAX || config == NULL) {
return -1; return -1;
} }
g_channels[channel].config = *config; g_channels[channel].config = *config;
return apply_uart_config(channel); return apply_uart_config(channel);
} }
@@ -208,29 +218,16 @@ int uart_trans_stop(uart_channel_t channel)
if (channel >= UART_CHANNEL_MAX) { if (channel >= UART_CHANNEL_MAX) {
return -1; return -1;
} }
HAL_UART_DMAStop(g_channels[channel].huart); HAL_UART_DMAStop(g_channels[channel].huart);
g_channels[channel].running = false; g_channels[channel].running = false;
g_channels[channel].tx_busy = false; g_channels[channel].tx_busy = false;
return 0; return 0;
} }
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats) void uart_trans_poll(void)
{ {
if (channel >= UART_CHANNEL_MAX || stats == NULL) { kick_tx(UART_CHANNEL_U0);
return; kick_tx(UART_CHANNEL_U1);
}
*stats = g_channels[channel].stats;
}
void uart_trans_reset_stats(uart_channel_t channel)
{
if (channel >= UART_CHANNEL_MAX) {
return;
}
memset(&g_channels[channel].stats, 0, sizeof(g_channels[channel].stats));
} }
uint16_t uart_trans_rx_available(uart_channel_t channel) uint16_t uart_trans_rx_available(uart_channel_t channel)
@@ -238,7 +235,6 @@ uint16_t uart_trans_rx_available(uart_channel_t channel)
if (channel >= UART_CHANNEL_MAX) { if (channel >= UART_CHANNEL_MAX) {
return 0u; return 0u;
} }
return ring_used(g_channels[channel].rx_head, g_channels[channel].rx_tail, UART_RX_RING_BUFFER_SIZE); return ring_used(g_channels[channel].rx_head, g_channels[channel].rx_tail, UART_RX_RING_BUFFER_SIZE);
} }
@@ -256,11 +252,9 @@ uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len
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) % UART_RX_RING_BUFFER_SIZE); ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % UART_RX_RING_BUFFER_SIZE);
} }
if (copied > 0u) { if (copied > 0u) {
ctx->stats.rx_packets++; ctx->stats.rx_packets++;
} }
return copied; return copied;
} }
@@ -287,10 +281,26 @@ uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t
return written; return written;
} }
void uart_trans_poll(void) uint16_t uart_trans_tx_free(uart_channel_t channel)
{ {
kick_tx(UART_CHANNEL_SERVER); if (channel >= UART_CHANNEL_MAX) {
kick_tx(UART_CHANNEL_CLIENT); 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)
{
if (channel < UART_CHANNEL_MAX && stats != NULL) {
*stats = g_channels[channel].stats;
}
}
void uart_trans_reset_stats(uart_channel_t channel)
{
if (channel < UART_CHANNEL_MAX) {
memset(&g_channels[channel].stats, 0, sizeof(g_channels[channel].stats));
}
} }
void uart_trans_idle_handler(uart_channel_t channel) void uart_trans_idle_handler(uart_channel_t channel)
@@ -301,14 +311,12 @@ void uart_trans_idle_handler(uart_channel_t channel)
if (channel >= UART_CHANNEL_MAX) { if (channel >= UART_CHANNEL_MAX) {
return; return;
} }
huart = g_channels[channel].huart; huart = g_channels[channel].huart;
g_channels[channel].stats.idle_events++; g_channels[channel].stats.idle_events++;
dma_write_index = (uint16_t)(UART_RX_DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx)); dma_write_index = (uint16_t)(UART_RX_DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx));
if (dma_write_index >= UART_RX_DMA_BUFFER_SIZE) { if (dma_write_index >= UART_RX_DMA_BUFFER_SIZE) {
dma_write_index = 0u; dma_write_index = 0u;
} }
process_rx_snapshot(channel, dma_write_index); process_rx_snapshot(channel, dma_write_index);
} }
@@ -317,7 +325,6 @@ void uart_trans_rx_half_cplt_handler(uart_channel_t channel)
if (channel >= UART_CHANNEL_MAX) { if (channel >= UART_CHANNEL_MAX) {
return; return;
} }
g_channels[channel].stats.rx_half_events++; g_channels[channel].stats.rx_half_events++;
process_rx_snapshot(channel, UART_RX_DMA_BUFFER_SIZE / 2u); process_rx_snapshot(channel, UART_RX_DMA_BUFFER_SIZE / 2u);
} }
@@ -327,7 +334,6 @@ void uart_trans_rx_cplt_handler(uart_channel_t channel)
if (channel >= UART_CHANNEL_MAX) { if (channel >= UART_CHANNEL_MAX) {
return; return;
} }
g_channels[channel].stats.rx_full_events++; g_channels[channel].stats.rx_full_events++;
process_rx_snapshot(channel, 0u); process_rx_snapshot(channel, 0u);
} }
@@ -337,9 +343,124 @@ void uart_trans_tx_cplt_handler(uart_channel_t channel)
if (channel >= UART_CHANNEL_MAX) { if (channel >= UART_CHANNEL_MAX) {
return; return;
} }
g_channels[channel].tx_busy = false; g_channels[channel].tx_busy = false;
g_channels[channel].stats.tx_bytes += g_channels[channel].tx_dma_len; g_channels[channel].stats.tx_bytes += g_channels[channel].tx_dma_len;
g_channels[channel].tx_dma_len = 0u; g_channels[channel].tx_dma_len = 0u;
kick_tx(channel); kick_tx(channel);
} }
bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame)
{
uart_channel_ctx_t *ctx;
uint8_t header[4];
uint8_t tail_byte;
uint16_t available;
uint16_t payload_len;
uint16_t sync_offset;
uint16_t total_len;
if (channel >= UART_CHANNEL_MAX || frame == NULL) {
return false;
}
ctx = &g_channels[channel];
for (;;) {
available = uart_trans_rx_available(channel);
if (available < 6u) {
return false;
}
sync_offset = available;
for (uint16_t i = 0u; i < available; ++i) {
uint8_t byte = 0u;
if (!ring_peek_byte(ctx, i, &byte)) {
return false;
}
if (byte == UART_MUX_SYNC) {
sync_offset = i;
break;
}
}
if (sync_offset == available) {
ring_drop_bytes(ctx, available);
return false;
}
if (sync_offset > 0u) {
ring_drop_bytes(ctx, sync_offset);
available = (uint16_t)(available - sync_offset);
}
if (available < 6u) {
return false;
}
if (!ring_peek_span(ctx, 1u, header, sizeof(header))) {
return false;
}
payload_len = (uint16_t)(((uint16_t)header[0] << 8) | header[1]);
if (payload_len > sizeof(frame->payload)) {
ring_drop_bytes(ctx, 1u);
continue;
}
total_len = (uint16_t)(payload_len + 6u);
if (available < total_len) {
return false;
}
if (!ring_peek_byte(ctx, (uint16_t)(total_len - 1u), &tail_byte)) {
return false;
}
if (tail_byte != UART_MUX_TAIL) {
ring_drop_bytes(ctx, 1u);
continue;
}
frame->src_id = header[2];
frame->dst_mask = header[3];
frame->payload_len = payload_len;
if (payload_len > 0u) {
if (!ring_peek_span(ctx, 5u, frame->payload, payload_len)) {
return false;
}
}
ring_drop_bytes(ctx, total_len);
return true;
}
}
bool uart_mux_encode_frame(uint8_t src_id,
uint8_t dst_mask,
const uint8_t *payload,
uint16_t payload_len,
uint8_t *out,
uint16_t *out_len,
uint16_t out_capacity)
{
uint16_t frame_len;
if (out == NULL || out_len == NULL) {
return false;
}
frame_len = (uint16_t)(payload_len + 6u);
if (frame_len > out_capacity) {
return false;
}
out[0] = UART_MUX_SYNC;
out[1] = (uint8_t)(payload_len >> 8);
out[2] = (uint8_t)(payload_len & 0xFFu);
out[3] = src_id;
out[4] = dst_mask;
if (payload_len > 0u && payload != NULL) {
memcpy(&out[5], payload, payload_len);
}
out[5 + payload_len] = UART_MUX_TAIL;
*out_len = frame_len;
return true;
}
+24 -15
View File
@@ -1,6 +1,6 @@
/** /**
* @file uart_trans.h * @file uart_trans.h
* @brief Bare-metal UART DMA/IDLE transport layer. * @brief Bare-metal UART DMA/IDLE transport and MUX framing helpers.
*/ */
#ifndef __UART_TRANS_H__ #ifndef __UART_TRANS_H__
@@ -14,28 +14,28 @@ extern "C" {
#endif #endif
typedef enum { typedef enum {
UART_CHANNEL_SERVER = 0, UART_CHANNEL_U0 = 0,
UART_CHANNEL_CLIENT = 1, UART_CHANNEL_U1 = 1,
UART_CHANNEL_MAX UART_CHANNEL_MAX
} uart_channel_t; } uart_channel_t;
#define UART_RX_DMA_BUFFER_SIZE 128u typedef struct {
#define UART_TX_DMA_BUFFER_SIZE 128u uint8_t src_id;
uint8_t dst_mask;
uint16_t payload_len;
uint8_t payload[256];
} uart_mux_frame_t;
#define UART_RX_DMA_BUFFER_SIZE 256u
#define UART_TX_DMA_BUFFER_SIZE 256u
#define UART_RX_RING_BUFFER_SIZE 512u #define UART_RX_RING_BUFFER_SIZE 512u
#define UART_TX_RING_BUFFER_SIZE 512u #define UART_TX_RING_BUFFER_SIZE 384u
#define UART_DEFAULT_BAUDRATE 115200u
typedef struct { typedef struct {
uint32_t baudrate; uint32_t baudrate;
uint8_t data_bits;
uint8_t stop_bits;
uint8_t parity;
} uart_config_t; } uart_config_t;
#define UART_DEFAULT_BAUDRATE 115200u
#define UART_DEFAULT_DATA_BITS 8u
#define UART_DEFAULT_STOP_BITS 1u
#define UART_DEFAULT_PARITY 0u
typedef struct { typedef struct {
uint32_t rx_bytes; uint32_t rx_bytes;
uint32_t tx_bytes; uint32_t tx_bytes;
@@ -55,15 +55,24 @@ 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);
void uart_trans_rx_half_cplt_handler(uart_channel_t channel); void uart_trans_rx_half_cplt_handler(uart_channel_t channel);
void uart_trans_rx_cplt_handler(uart_channel_t channel); void uart_trans_rx_cplt_handler(uart_channel_t channel);
void uart_trans_tx_cplt_handler(uart_channel_t channel); void uart_trans_tx_cplt_handler(uart_channel_t channel);
bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame);
bool uart_mux_encode_frame(uint8_t src_id,
uint8_t dst_mask,
const uint8_t *payload,
uint16_t payload_len,
uint8_t *out,
uint16_t *out_len,
uint16_t out_capacity);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif #endif /* __UART_TRANS_H__ */
+105
View File
@@ -0,0 +1,105 @@
# CH390 最终结论报告
## 结论
本轮循环调试的最终结论是:
1. 当前工程中的主要软件问题已经完成收敛和清理。
2. CH390D 驱动、lwIP `netif`、ARP 与 ICMP 基本链路已经在实机上打通。
3. 本轮最终根因已确认不是普通软件逻辑错误,而是 CH390D 相关供电滤波电容虚焊,导致供电不稳定。
## 已完成的软件侧工作
本轮已完成并验证的事项包括:
1. 修复 PHY 访问无超时导致的永久卡死风险。
2. 修复未初始化 IWDG 句柄刷新导致的 HardFault。
3. 清理 CH390 运行时中断屏蔽范围,消除阻塞式 SPI 访问造成的运行时假死。
4. 重构 CH390 运行时所有权,避免多层并发触达底层 SPI 路径。
5.`main()` 中移除重复 CH390 复位,避免启动阶段额外复位噪声。
6. 清理已确认 warning 来源,避免无效变量继续污染构建结果。
7. 增加 CH390 identity gate,避免在无效寄存器读回前继续执行默认配置和 PHY 初始化。
8. 曾增加 bit-bang 诊断读用于快速隔离问题,该临时调试路径已在当前代码中移除。
## 实机关键证据
### 1. MCU 自身正常工作
已验证:
1. RTT 正常输出。
2. 主循环正常运行。
3. `TIM4` 心跳正常。
4. 运行期不再出现此前已修复的 HardFault 和“长时间假死”症状。
### 2. 最终硬件根因已定位
最终实板排查结果:
1. 板载一颗 CH390D 供电相关滤波电容存在虚焊。
2. 该问题导致 CH390D 供电不稳定,表现为寄存器读写、链路状态和报文收发在调试过程中不一致。
3. 修复硬件后,实机已观察到:
- `VID=0x1C00``PID=0x9151``REV=0x2B`
- PHY 寄存器稳定可读
- `lwIP netif` 能进入 `LINK_UP`
- 设备可接收 ARP request 并发出 ARP reply
- 设备可接收 ICMP Echo Request 并发出 Echo Reply
### 3. 历史 bit-bang 对照结果(已归档)
在早期调试中,曾绕过 STM32 硬件 SPI 外设、直接用 GPIO 软件时序读取 `VIDL/VIDH/PIDL/PIDH/CHIPR`RTT 输出为:
```text
CH390 bitbang VIDL=0xFF VIDH=0xFF PIDL=0xFF PIDH=0xFF CHIPR=0xFF
```
该历史证据用于定位阶段,当前仅保留结论,不再保留对应代码路径。它说明:
1. 在硬件未修复前,单看软件现象会误导排查方向。
2. 电源完整性问题会放大为看似“SPI/IRQ/RX/TX 都可疑”的复合症状。
## 外部参考对结论的支撑
对公开 CH390 / DM9051 实现的对照结果表明:
1. CH390 SPI 访问时序、模式选择和 RX SRAM 连续事务仍然值得严格对照参考实现。
2. 但本项目最终问题并非“参考实现缺失”,而是硬件供电缺陷放大了调试噪声。
3. 外部参考对软件排查有帮助,但不能替代板级供电与焊接检查。
## 当前最可信判断
最终确认的板级问题为:
1. CH390D 供电滤波电容虚焊。
2. 该虚焊导致供电稳定性不足,从而引出不稳定的寄存器读写、链路与收发行为。
## 版本库状态
本轮已创建一个阶段性 checkpoint commit
1. `1808f99` `fix: harden CH390 bring-up diagnostics`
该提交记录了:
1. warning 清理
2. 移除重复复位
3. CH390 早期 identity gate
4. 链路变化稳定等待
## 推荐的下一步
后续更高价值的工作不再是继续怀疑 CH390 是否“完全不通”,而是:
1. 在硬件问题修复后补充长时间稳定性测试。
2. 验证 TCP Server / TCP Client 业务流量与桥接逻辑在修复硬件后的行为。
3. 保持驱动层日志最小化,仅在重新排障时按需开启详细 RTT。
## 收尾说明
本轮循环的退出条件已经满足:软件主路径已验证,且硬件根因已定位。
因此当前最合理的结论是:
1. CH390D 驱动、lwIP `netif`、ARP 和 ICMP 基本链路已在实机打通。
2. 本轮真正拦路的不是普通软件逻辑,而是板级供电滤波电容虚焊。
3. 后续应在硬件修复后的稳定板卡上继续推进应用层联调与文档收口。
+1 -1
View File
@@ -38,7 +38,7 @@ void MX_IWDG_Init(void)
/* USER CODE END IWDG_Init 1 */ /* USER CODE END IWDG_Init 1 */
hiwdg.Instance = IWDG; hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_4; hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
hiwdg.Init.Reload = 4095; hiwdg.Init.Reload = 4095;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
{ {
+536 -251
View File
@@ -4,16 +4,6 @@
* @file : main.c * @file : main.c
* @brief : Main program body * @brief : Main program body
****************************************************************************** ******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/ */
/* USER CODE END Header */ /* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/ /* Includes ------------------------------------------------------------------*/
@@ -34,8 +24,8 @@
#include "CH390_Interface.h" #include "CH390_Interface.h"
#include "SEGGER_RTT.h" #include "SEGGER_RTT.h"
#include "config.h" #include "config.h"
#include "flash_param.h"
#include "ethernetif.h" #include "ethernetif.h"
#include "ch390_runtime.h"
#include "lwip/init.h" #include "lwip/init.h"
#include "lwip/timeouts.h" #include "lwip/timeouts.h"
#include "tcp_client.h" #include "tcp_client.h"
@@ -43,80 +33,74 @@
#include "uart_trans.h" #include "uart_trans.h"
/* USER CODE END Includes */ /* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */ /* USER CODE BEGIN PD */
/* CH390 硬件控制引脚 */ #define LED_PIN GPIO_PIN_13
#define CH390_RST_PIN GPIO_PIN_1 #define LED_PORT GPIOC
#define CH390_RST_PORT GPIOB #define APP_ROUTE_BUFFER_SIZE 256u
#define CH390_CS_PIN GPIO_PIN_4 #define APP_TCP_TO_UART_CHUNK_SIZE 128u
#define CH390_CS_PORT GPIOA #define STACK_GUARD_WORD 0xA5A5A5A5u
#define APP_HEALTH_CHECK_INTERVAL_MS 5000u
/* LED 指示灯 */
#define LED_PIN GPIO_PIN_13
#define LED_PORT GPIOC
/* USER CODE END PD */ /* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */ /* USER CODE BEGIN PV */
static volatile uint16_t g_led_blink_ticks = 0; static volatile uint16_t g_led_blink_ticks = 0;
static uint8_t g_clock_fallback_to_hsi = 0u; static uint8_t g_clock_fallback_to_hsi = 0u;
volatile uint8_t g_uart1_rx_probe_byte = 0u; volatile uint8_t g_uart1_rx_probe_byte = 0u;
static uint8_t g_stack_guard_reported = 0u;
static uint8_t g_mux_response_frame[272];
static uint8_t g_links_started = 0u;
/* USER CODE END PV */ /* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); void SystemClock_Config(void);
/* USER CODE BEGIN PFP */ /* USER CODE BEGIN PFP */
static void CH390_HardwareReset(void);
static void LED_Init(void); static void LED_Init(void);
static void LED_StartBlink(void); static void LED_StartBlink(void);
static void BootDiag_ReportCh390(void); static void BootDiag_ReportCh390(void);
static void App_PollUart1ConfigRx(void);
static void App_Init(void); static void App_Init(void);
static void App_Poll(void); static void App_Poll(void);
static void App_ConfigureLinks(const device_config_t *cfg);
static void App_RouteRawUartTraffic(void);
static void App_RouteMuxUartTraffic(void);
static void App_RouteTcpTraffic(void);
static void StackGuard_Init(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 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_SendTcpClientPayload(uint8_t instance, const uint8_t *data, uint16_t len);
/* USER CODE END PFP */ /* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/ /* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */ /* USER CODE BEGIN 0 */
extern uint32_t Stack_Mem[];
/**
* @brief CH390 硬件复位
* @note 复位时序: RST 低电平至少 10us,然后高电平等待 50ms 完成初始化
*/
static void CH390_HardwareReset(void)
{
/* 拉低 RST 引脚 */
HAL_GPIO_WritePin(CH390_RST_PORT, CH390_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(1); /* 保持低电平 1ms (远超最小 10us 要求) */
/* 拉高 RST 引脚,等待芯片初始化完成 */
HAL_GPIO_WritePin(CH390_RST_PORT, CH390_RST_PIN, GPIO_PIN_SET);
HAL_Delay(50); /* 等待 50ms */
/* 确保 CS 为高电平(未选中状态) */
HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, GPIO_PIN_SET);
}
/**
* @brief LED 初始化(点亮表示系统启动)
*/
static void LED_Init(void) static void LED_Init(void)
{ {
/* LED 灭(PC13 高电平灭,低电平亮) */
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET);
} }
static void StackGuard_Init(void)
{
Stack_Mem[0] = STACK_GUARD_WORD;
g_stack_guard_reported = 0u;
}
static void StackGuard_Check(void)
{
if (Stack_Mem[0] != STACK_GUARD_WORD) {
if (g_stack_guard_reported == 0u) {
g_stack_guard_reported = 1u;
SEGGER_RTT_WriteString(0, "ERROR: Main stack guard overwritten\r\n");
}
__disable_irq();
NVIC_SystemReset();
}
}
static void LED_StartBlink(void) static void LED_StartBlink(void)
{ {
if (HAL_TIM_Base_Start_IT(&htim4) != HAL_OK) { if (HAL_TIM_Base_Start_IT(&htim4) != HAL_OK) {
@@ -124,9 +108,6 @@ static void LED_StartBlink(void)
} }
} }
/**
* @brief LED 闪烁(用于指示系统运行状态)
*/
void LED_Toggle(void) void LED_Toggle(void)
{ {
HAL_GPIO_TogglePin(LED_PORT, LED_PIN); HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
@@ -139,79 +120,127 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
if (g_led_blink_ticks >= 1000u) { if (g_led_blink_ticks >= 1000u) {
g_led_blink_ticks = 0u; g_led_blink_ticks = 0u;
LED_Toggle(); LED_Toggle();
if (hiwdg.Instance == IWDG) {
HAL_IWDG_Refresh(&hiwdg);
}
} }
} }
} }
static void BootDiag_ReportCh390(void) static void BootDiag_ReportCh390(void)
{ {
uint16_t vendor_id; ch390_diag_t diag;
uint16_t product_id; const device_config_t *cfg = config_get();
uint8_t revision; uint8_t mac_hw[6];
uint8_t nsr;
uint8_t ncr;
uint8_t rcr;
uint8_t imr;
uint8_t intcr;
uint8_t gpr;
uint8_t isr;
uint8_t ncr_before;
uint8_t ncr_after;
uint8_t intcr_before;
uint8_t intcr_after;
int link_status;
vendor_id = ch390_get_vendor_id(); ch390_runtime_get_diag(&diag);
product_id = ch390_get_product_id(); ch390_get_mac(mac_hw);
revision = ch390_get_revision();
nsr = ch390_read_reg(CH390_NSR);
ncr = ch390_read_reg(CH390_NCR);
rcr = ch390_read_reg(CH390_RCR);
imr = ch390_read_reg(CH390_IMR);
intcr = ch390_read_reg(CH390_INTCR);
gpr = ch390_read_reg(CH390_GPR);
isr = ch390_read_reg(CH390_ISR);
link_status = ch390_get_link_status();
ncr_before = ncr;
ch390_write_reg(CH390_NCR, (uint8_t)(ncr_before ^ NCR_FDX));
ncr_after = ch390_read_reg(CH390_NCR);
ch390_write_reg(CH390_NCR, ncr_before);
intcr_before = intcr;
ch390_write_reg(CH390_INTCR, (uint8_t)(INCR_TYPE_OD | INCR_POL_L));
intcr_after = ch390_read_reg(CH390_INTCR);
ch390_write_reg(CH390_INTCR, intcr_before);
SEGGER_RTT_printf(0, SEGGER_RTT_printf(0,
"CH390 VID=0x%04X PID=0x%04X REV=0x%02X NSR=0x%02X LINK=%d\r\n", "CH390 VID=0x%04X PID=0x%04X REV=0x%02X LINK=%u MAC=%02X:%02X:%02X:%02X:%02X:%02X\r\n",
vendor_id, diag.vendor_id,
product_id, diag.product_id,
revision, diag.revision,
nsr, diag.link_up,
link_status); mac_hw[0], mac_hw[1], mac_hw[2], mac_hw[3], mac_hw[4], mac_hw[5]);
SEGGER_RTT_printf(0, SEGGER_RTT_printf(0,
"CH390 NCR=0x%02X RCR=0x%02X IMR=0x%02X INTCR=0x%02X GPR=0x%02X ISR=0x%02X\r\n", "NET cfg IP=%u.%u.%u.%u MASK=%u.%u.%u.%u GW=%u.%u.%u.%u MUX=%u\r\n",
ncr, cfg->net.ip[0], cfg->net.ip[1], cfg->net.ip[2], cfg->net.ip[3],
rcr, cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3],
imr, cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3],
intcr, cfg->mux_mode);
gpr,
isr);
SEGGER_RTT_printf(0, SEGGER_RTT_printf(0,
"CH390 WRCHK NCR:0x%02X->0x%02X INTCR:0x%02X->0x%02X\r\n", "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",
ncr_before, (unsigned int)diag.rx_packets_ok,
ncr_after, (unsigned int)diag.rx_packets_drop,
intcr_before, (unsigned int)diag.rx_pbuf_alloc_failed,
intcr_after); (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_PollUart1ConfigRx(void) static void App_ConfigureLinks(const device_config_t *cfg)
{ {
while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) { tcp_server_instance_config_t server_cfg;
uint8_t byte = (uint8_t)(huart1.Instance->DR & 0xFFu); tcp_client_instance_config_t client_cfg;
config_uart_rx_byte(byte);
(void)tcp_server_init_all();
(void)tcp_client_init_all();
server_cfg.enabled = (cfg->links[CONFIG_LINK_S1].enabled != 0u);
server_cfg.port = cfg->links[CONFIG_LINK_S1].local_port;
(void)tcp_server_config(0u, &server_cfg);
server_cfg.enabled = (cfg->links[CONFIG_LINK_S2].enabled != 0u);
server_cfg.port = cfg->links[CONFIG_LINK_S2].local_port;
(void)tcp_server_config(1u, &server_cfg);
memcpy(client_cfg.remote_ip, cfg->links[CONFIG_LINK_C1].remote_ip, sizeof(client_cfg.remote_ip));
client_cfg.local_port = cfg->links[CONFIG_LINK_C1].local_port;
client_cfg.remote_port = cfg->links[CONFIG_LINK_C1].remote_port;
client_cfg.reconnect_interval_ms = TCP_CLIENT_RECONNECT_DELAY_MS;
client_cfg.enabled = (cfg->links[CONFIG_LINK_C1].enabled != 0u);
client_cfg.auto_reconnect = true;
(void)tcp_client_config(0u, &client_cfg);
memcpy(client_cfg.remote_ip, cfg->links[CONFIG_LINK_C2].remote_ip, sizeof(client_cfg.remote_ip));
client_cfg.local_port = cfg->links[CONFIG_LINK_C2].local_port;
client_cfg.remote_port = cfg->links[CONFIG_LINK_C2].remote_port;
client_cfg.enabled = (cfg->links[CONFIG_LINK_C2].enabled != 0u);
(void)tcp_client_config(1u, &client_cfg);
}
static void App_StartLinksIfNeeded(void)
{
int any_failed;
if ((g_links_started != 0u) || !netif_is_link_up(&ch390_netif)) {
return;
} }
any_failed = 0;
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
if (tcp_server_start(i) != 0) {
any_failed = 1;
}
}
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
if (tcp_client_connect(i) != 0) {
any_failed = 1;
}
}
if (any_failed) {
SEGGER_RTT_WriteString(0, "NET links start partially failed, will retry\r\n");
return;
}
g_links_started = 1u;
SEGGER_RTT_WriteString(0, "NET links started after link-up\r\n");
}
static void App_StopLinksIfNeeded(void)
{
if (netif_is_link_up(&ch390_netif)) {
return;
}
if (g_links_started != 0u) {
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
(void)tcp_client_disconnect(i);
}
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
(void)tcp_server_stop(i);
}
SEGGER_RTT_WriteString(0, "NET links stopped after link-down\r\n");
}
g_links_started = 0u;
} }
static void App_Init(void) static void App_Init(void)
@@ -221,91 +250,423 @@ static void App_Init(void)
ip4_addr_t netmask; ip4_addr_t netmask;
ip4_addr_t gateway; ip4_addr_t gateway;
uart_config_t uart_cfg; uart_config_t uart_cfg;
tcp_server_config_t server_cfg;
tcp_client_config_t client_cfg;
config_init(); (void)config_init();
cfg = config_get(); cfg = config_get();
uart_trans_init(); (void)uart_trans_init();
uart_cfg.baudrate = cfg->uart_baudrate[0];
uart_cfg.baudrate = cfg->uart2_baudrate; (void)uart_trans_config(UART_CHANNEL_U0, &uart_cfg);
uart_cfg.data_bits = cfg->uart2_databits; uart_cfg.baudrate = cfg->uart_baudrate[1];
uart_cfg.stop_bits = cfg->uart2_stopbits; (void)uart_trans_config(UART_CHANNEL_U1, &uart_cfg);
uart_cfg.parity = cfg->uart2_parity; (void)uart_trans_start(UART_CHANNEL_U0);
uart_trans_config(UART_CHANNEL_SERVER, &uart_cfg); (void)uart_trans_start(UART_CHANNEL_U1);
uart_cfg.baudrate = cfg->uart3_baudrate;
uart_cfg.data_bits = cfg->uart3_databits;
uart_cfg.stop_bits = cfg->uart3_stopbits;
uart_cfg.parity = cfg->uart3_parity;
uart_trans_config(UART_CHANNEL_CLIENT, &uart_cfg);
uart_trans_start(UART_CHANNEL_SERVER);
uart_trans_start(UART_CHANNEL_CLIENT);
SEGGER_RTT_Init(); SEGGER_RTT_Init();
StackGuard_Init();
SEGGER_RTT_WriteString(0, "\r\nTCP2UART boot\r\n"); SEGGER_RTT_WriteString(0, "\r\nTCP2UART boot\r\n");
if (g_clock_fallback_to_hsi != 0u) { if (g_clock_fallback_to_hsi != 0u) {
SEGGER_RTT_WriteString(0, "WARN: HSE start failed, fallback to HSI PLL\r\n"); SEGGER_RTT_WriteString(0, "WARN: HSE start failed, fallback to HSI PLL\r\n");
} }
lwip_init(); lwip_init();
IP4_ADDR(&ipaddr, cfg->ip[0], cfg->ip[1], cfg->ip[2], cfg->ip[3]); IP4_ADDR(&ipaddr, cfg->net.ip[0], cfg->net.ip[1], cfg->net.ip[2], cfg->net.ip[3]);
IP4_ADDR(&netmask, cfg->mask[0], cfg->mask[1], cfg->mask[2], cfg->mask[3]); IP4_ADDR(&netmask, cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3]);
IP4_ADDR(&gateway, cfg->gw[0], cfg->gw[1], cfg->gw[2], cfg->gw[3]); IP4_ADDR(&gateway, cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3]);
lwip_netif_init(&ipaddr, &netmask, &gateway); lwip_netif_init(&ipaddr, &netmask, &gateway);
App_ConfigureLinks(cfg);
BootDiag_ReportCh390(); BootDiag_ReportCh390();
server_cfg.port = cfg->server_port;
server_cfg.auto_reconnect = true;
tcp_server_init(&server_cfg);
tcp_server_start();
memcpy(client_cfg.server_ip, cfg->remote_ip, sizeof(client_cfg.server_ip));
client_cfg.server_port = cfg->remote_port;
client_cfg.auto_reconnect = true;
client_cfg.reconnect_interval_ms = cfg->reconnect_interval;
tcp_client_init(&client_cfg);
tcp_client_connect();
/* Arm UART1 RX interrupt path so config commands can enter via USART1. */
if (HAL_UART_Receive_IT(&huart1, (uint8_t *)&g_uart1_rx_probe_byte, 1u) != HAL_OK) { if (HAL_UART_Receive_IT(&huart1, (uint8_t *)&g_uart1_rx_probe_byte, 1u) != HAL_OK) {
Error_Handler(); Error_Handler();
} }
} }
static bool App_SendTcpServerPayload(uint8_t instance, const uint8_t *data, uint16_t len)
{
return tcp_server_send(instance, data, len) == (int)len;
}
static bool App_SendTcpClientPayload(uint8_t instance, const uint8_t *data, uint16_t len)
{
return tcp_client_send(instance, data, len) == (int)len;
}
static bool App_SendToUart(uint8_t uart_index, uint8_t src_id, uint8_t dst_mask, const uint8_t *data, uint16_t len)
{
const device_config_t *cfg = config_get();
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
uint16_t written;
if (cfg->mux_mode == MUX_MODE_FRAME) {
uint8_t frame[APP_ROUTE_BUFFER_SIZE + 6u];
uint16_t frame_len = 0u;
if (uart_mux_encode_frame(src_id, dst_mask, data, len, frame, &frame_len, sizeof(frame))) {
written = uart_trans_write(channel, frame, frame_len);
return written == frame_len;
}
return false;
} else {
written = uart_trans_write(channel, data, len);
return written == len;
}
}
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)
{
const device_config_t *cfg = config_get();
uint8_t buffer[APP_TCP_TO_UART_CHUNK_SIZE];
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
uint16_t available = tcp_server_rx_available(i);
if (available > 0u) {
uint8_t link_index = (i == 0u) ? CONFIG_LINK_S1 : CONFIG_LINK_S2;
uint8_t uart_index = cfg->links[link_index].uart;
uint8_t src_id = config_link_index_to_endpoint(link_index);
uint8_t dst_mask = config_uart_index_to_endpoint(uart_index);
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
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;
}
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) {
uint16_t available = tcp_client_rx_available(i);
if (available > 0u) {
uint8_t link_index = (i == 0u) ? CONFIG_LINK_C1 : CONFIG_LINK_C2;
uint8_t uart_index = cfg->links[link_index].uart;
uint8_t src_id = config_link_index_to_endpoint(link_index);
uint8_t dst_mask = config_uart_index_to_endpoint(uart_index);
uart_channel_t channel = (uart_index == LINK_UART_U1) ? UART_CHANNEL_U1 : UART_CHANNEL_U0;
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;
}
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;
}
}
}
}
}
static void App_RouteRawUartTraffic(void)
{
const device_config_t *cfg = config_get();
uint8_t buffer[APP_ROUTE_BUFFER_SIZE];
uint16_t len;
len = uart_trans_read(UART_CHANNEL_U0, buffer, sizeof(buffer));
if (len > 0u) {
bool routed_ok = true;
for (uint8_t i = 0; i < CONFIG_LINK_COUNT; ++i) {
bool sent = true;
if (cfg->links[i].enabled == 0u || cfg->links[i].uart != LINK_UART_U0) {
continue;
}
if (i == CONFIG_LINK_S1) {
sent = App_SendTcpServerPayload(0u, buffer, len);
} else if (i == CONFIG_LINK_S2) {
sent = App_SendTcpServerPayload(1u, buffer, len);
} else if (i == CONFIG_LINK_C1) {
sent = App_SendTcpClientPayload(0u, buffer, len);
} else if (i == CONFIG_LINK_C2) {
sent = App_SendTcpClientPayload(1u, buffer, len);
}
if (!sent) {
routed_ok = false;
}
}
if (!routed_ok) {
return;
}
}
len = uart_trans_read(UART_CHANNEL_U1, buffer, sizeof(buffer));
if (len > 0u) {
bool routed_ok = true;
for (uint8_t i = 0; i < CONFIG_LINK_COUNT; ++i) {
bool sent = true;
if (cfg->links[i].enabled == 0u || cfg->links[i].uart != LINK_UART_U1) {
continue;
}
if (i == CONFIG_LINK_S1) {
sent = App_SendTcpServerPayload(0u, buffer, len);
} else if (i == CONFIG_LINK_S2) {
sent = App_SendTcpServerPayload(1u, buffer, len);
} else if (i == CONFIG_LINK_C1) {
sent = App_SendTcpClientPayload(0u, buffer, len);
} else if (i == CONFIG_LINK_C2) {
sent = App_SendTcpClientPayload(1u, buffer, len);
}
if (!sent) {
routed_ok = false;
}
}
if (!routed_ok) {
return;
}
}
}
static void App_RouteMuxUartTraffic(void)
{
uart_mux_frame_t frame;
const device_config_t *cfg = config_get();
bool routed_ok;
while (uart_mux_try_extract_frame(UART_CHANNEL_U0, &frame)) {
#if defined(DEBUG) && (DEBUG != 0)
SEGGER_RTT_printf(0, "Mux frame from UART0: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len);
#endif
if (frame.dst_mask == 0u) {
at_result_t result;
char *response_text = (char *)&g_mux_response_frame[5];
if (config_build_response_frame(frame.payload, frame.payload_len, response_text, (uint16_t)(sizeof(g_mux_response_frame) - 6u), &result)) {
uint16_t response_len = (uint16_t)strlen(response_text);
uint16_t frame_len = 0u;
if (uart_mux_encode_frame(config_uart_index_to_endpoint(LINK_UART_U0), 0u, (const uint8_t *)response_text, response_len, g_mux_response_frame, &frame_len, sizeof(g_mux_response_frame))) {
if (uart_trans_write(UART_CHANNEL_U0, g_mux_response_frame, frame_len) != frame_len) {
return;
}
}
if (result == AT_NEED_REBOOT) {
static const char hint[] = "Note: Use AT+SAVE then AT+RESET to apply changes\r\n";
response_len = (uint16_t)strlen(hint);
if (uart_mux_encode_frame(config_uart_index_to_endpoint(LINK_UART_U0), 0u, (const uint8_t *)hint, response_len, g_mux_response_frame, &frame_len, sizeof(g_mux_response_frame))) {
if (uart_trans_write(UART_CHANNEL_U0, g_mux_response_frame, frame_len) != frame_len) {
return;
}
}
}
}
continue;
}
routed_ok = true;
if ((frame.dst_mask & ENDPOINT_S1) != 0u) {
routed_ok = App_SendTcpServerPayload(0u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_S2) != 0u) {
routed_ok = App_SendTcpServerPayload(1u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_C1) != 0u) {
routed_ok = App_SendTcpClientPayload(0u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_C2) != 0u) {
routed_ok = App_SendTcpClientPayload(1u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_UART3) != 0u && cfg->links[CONFIG_LINK_S2].uart == LINK_UART_U1) {
routed_ok = App_SendToUart(LINK_UART_U1, frame.src_id, ENDPOINT_UART3, frame.payload, frame.payload_len) && routed_ok;
}
if (!routed_ok) {
return;
}
}
while (uart_mux_try_extract_frame(UART_CHANNEL_U1, &frame)) {
#if defined(DEBUG) && (DEBUG != 0)
SEGGER_RTT_printf(0, "Mux frame from UART1: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len);
#endif
if (frame.dst_mask == 0u) {
at_result_t result;
char *response_text = (char *)&g_mux_response_frame[5];
if (config_build_response_frame(frame.payload, frame.payload_len, response_text, (uint16_t)(sizeof(g_mux_response_frame) - 6u), &result)) {
uint16_t response_len = (uint16_t)strlen(response_text);
uint16_t frame_len = 0u;
if (uart_mux_encode_frame(config_uart_index_to_endpoint(LINK_UART_U1), 0u, (const uint8_t *)response_text, response_len, g_mux_response_frame, &frame_len, sizeof(g_mux_response_frame))) {
if (uart_trans_write(UART_CHANNEL_U1, g_mux_response_frame, frame_len) != frame_len) {
return;
}
}
if (result == AT_NEED_REBOOT) {
static const char hint[] = "Note: Use AT+SAVE then AT+RESET to apply changes\r\n";
response_len = (uint16_t)strlen(hint);
if (uart_mux_encode_frame(config_uart_index_to_endpoint(LINK_UART_U1), 0u, (const uint8_t *)hint, response_len, g_mux_response_frame, &frame_len, sizeof(g_mux_response_frame))) {
if (uart_trans_write(UART_CHANNEL_U1, g_mux_response_frame, frame_len) != frame_len) {
return;
}
}
}
}
continue;
}
routed_ok = true;
if ((frame.dst_mask & ENDPOINT_S1) != 0u) {
routed_ok = App_SendTcpServerPayload(0u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_S2) != 0u) {
routed_ok = App_SendTcpServerPayload(1u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_C1) != 0u) {
routed_ok = App_SendTcpClientPayload(0u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_C2) != 0u) {
routed_ok = App_SendTcpClientPayload(1u, frame.payload, frame.payload_len) && routed_ok;
}
if ((frame.dst_mask & ENDPOINT_UART2) != 0u) {
routed_ok = App_SendToUart(LINK_UART_U0, frame.src_id, ENDPOINT_UART2, frame.payload, frame.payload_len) && routed_ok;
}
if (!routed_ok) {
return;
}
}
}
static void App_Poll(void) static void App_Poll(void)
{ {
uint8_t buffer[128]; static uint32_t s_health_check_tick;
int len; uint32_t now;
ethernetif_poll(); ethernetif_poll();
ethernetif_check_link(); ethernetif_check_link();
sys_check_timeouts(); sys_check_timeouts();
App_StopLinksIfNeeded();
App_StartLinksIfNeeded();
tcp_server_poll();
tcp_client_poll(); tcp_client_poll();
uart_trans_poll(); uart_trans_poll();
App_PollUart1ConfigRx(); StackGuard_Check();
config_poll(); config_poll();
App_RouteTcpTraffic();
len = tcp_server_recv(buffer, sizeof(buffer), 0u); if (config_get()->mux_mode == MUX_MODE_FRAME) {
if (len > 0) { App_RouteMuxUartTraffic();
uart_trans_write(UART_CHANNEL_SERVER, buffer, (uint16_t)len); } else {
App_RouteRawUartTraffic();
} }
len = tcp_client_recv(buffer, sizeof(buffer), 0u); now = HAL_GetTick();
if (len > 0) { if ((now - s_health_check_tick) >= APP_HEALTH_CHECK_INTERVAL_MS) {
uart_trans_write(UART_CHANNEL_CLIENT, buffer, (uint16_t)len); s_health_check_tick = now;
} ch390_runtime_health_check(&ch390_netif);
len = (int)uart_trans_read(UART_CHANNEL_SERVER, buffer, sizeof(buffer));
if (len > 0) {
tcp_server_send(buffer, (uint16_t)len);
}
len = (int)uart_trans_read(UART_CHANNEL_CLIENT, buffer, sizeof(buffer));
if (len > 0) {
tcp_client_send(buffer, (uint16_t)len);
} }
if (config_is_reset_requested()) { if (config_is_reset_requested()) {
@@ -313,83 +674,38 @@ static void App_Poll(void)
NVIC_SystemReset(); NVIC_SystemReset();
} }
HAL_IWDG_Refresh(&hiwdg);
} }
/* USER CODE END 0 */ /* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void) int main(void)
{ {
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init(); HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config(); SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init(); MX_GPIO_Init();
MX_DMA_Init(); MX_DMA_Init();
MX_IWDG_Init();
MX_USART1_UART_Init(); MX_USART1_UART_Init();
MX_USART2_UART_Init(); MX_USART2_UART_Init();
MX_USART3_UART_Init(); MX_USART3_UART_Init();
MX_SPI1_Init(); MX_SPI1_Init();
MX_TIM4_Init(); MX_TIM4_Init();
/* USER CODE BEGIN 2 */ MX_IWDG_Init();
/* LED 初始化 */
LED_Init(); LED_Init();
LED_StartBlink(); LED_StartBlink();
App_Init(); App_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) while (1)
{ {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
App_Poll(); App_Poll();
} }
/* USER CODE END 3 */
} }
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void) void SystemClock_Config(void)
{ {
RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
g_clock_fallback_to_hsi = 0u; g_clock_fallback_to_hsi = 0u;
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
@@ -399,11 +715,6 @@ void SystemClock_Config(void)
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
/*
* Some bring-up boards fail to start the external crystal cleanly.
* Fall back to HSI-based PLL so the firmware can still boot and expose
* RTT / debugger evidence instead of trapping during clock init.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSEState = RCC_HSE_OFF; RCC_OscInitStruct.HSEState = RCC_HSE_OFF;
RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSIState = RCC_HSI_ON;
@@ -417,8 +728,6 @@ void SystemClock_Config(void)
g_clock_fallback_to_hsi = 1u; g_clock_fallback_to_hsi = 1u;
} }
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
@@ -432,11 +741,6 @@ void SystemClock_Config(void)
} }
} }
/* USER CODE BEGIN 4 */
/**
* @brief 重定向 printf 到 UART1(调试输出)
*/
#ifdef __GNUC__ #ifdef __GNUC__
int _write(int file, char *ptr, int len) int _write(int file, char *ptr, int len)
{ {
@@ -464,35 +768,16 @@ void Debug_TrapWithRttHint(const char *tag)
} }
} }
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void) void Error_Handler(void)
{ {
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
Debug_TrapWithRttHint("Error_Handler"); Debug_TrapWithRttHint("Error_Handler");
/* USER CODE END Error_Handler_Debug */
} }
#ifdef USE_FULL_ASSERT #ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line) void assert_failed(uint8_t *file, uint32_t line)
{ {
/* USER CODE BEGIN 6 */
(void)file; (void)file;
(void)line; (void)line;
Debug_TrapWithRttHint("assert_failed"); Debug_TrapWithRttHint("assert_failed");
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
} }
#endif /* USE_FULL_ASSERT */ #endif
+3 -3
View File
@@ -41,10 +41,10 @@ void MX_SPI1_Init(void)
hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; /* CH390 requires CPOL=High (Mode 3) */ hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; /* Match CH390 runtime baseline: CPOL=Low */
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; /* CH390 requires CPHA=2Edge (Mode 3) */ hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; /* Match CH390 runtime baseline: CPHA=1Edge (Mode 0) */
hspi1.Init.NSS = SPI_NSS_SOFT; /* Software CS control for CH390 */ hspi1.Init.NSS = SPI_NSS_SOFT; /* Software CS control for CH390 */
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; /* 72MHz/8 = 9MHz (CH390 max 10MHz) */ hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; /* 72MHz/2 = 36MHz, max SPI1 clock at current APB2 */
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
+10 -235
View File
@@ -4,61 +4,15 @@
* @file stm32f1xx_it.c * @file stm32f1xx_it.c
* @brief Interrupt Service Routines. * @brief Interrupt Service Routines.
****************************************************************************** ******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/ */
/* USER CODE END Header */ /* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h" #include "main.h"
#include "stm32f1xx_it.h" #include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ethernetif.h" #include "ethernetif.h"
#include "SEGGER_RTT.h"
#include "uart_trans.h" #include "uart_trans.h"
#include "config.h" #include "config.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/* External variables --------------------------------------------------------*/
extern SPI_HandleTypeDef hspi1; extern SPI_HandleTypeDef hspi1;
extern TIM_HandleTypeDef htim4; extern TIM_HandleTypeDef htim4;
extern DMA_HandleTypeDef hdma_usart1_rx; extern DMA_HandleTypeDef hdma_usart1_rx;
@@ -70,332 +24,153 @@ extern DMA_HandleTypeDef hdma_usart3_tx;
extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2; extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3; extern UART_HandleTypeDef huart3;
/* USER CODE BEGIN EV */ extern volatile uint8_t g_uart1_rx_probe_byte;
/* USER CODE END EV */
/******************************************************************************/
/* Cortex-M3 Processor Interruption and Exception Handlers */
/******************************************************************************/
/**
* @brief This function handles Non maskable interrupt.
*/
void NMI_Handler(void) void NMI_Handler(void)
{ {
/* USER CODE BEGIN NonMaskableInt_IRQn 0 */
/* USER CODE END NonMaskableInt_IRQn 0 */
/* USER CODE BEGIN NonMaskableInt_IRQn 1 */
Debug_TrapWithRttHint("NMI_Handler"); Debug_TrapWithRttHint("NMI_Handler");
/* USER CODE END NonMaskableInt_IRQn 1 */
} }
/**
* @brief This function handles Hard fault interrupt.
*/
void HardFault_Handler(void) void HardFault_Handler(void)
{ {
/* USER CODE BEGIN HardFault_IRQn 0 */
/* USER CODE END HardFault_IRQn 0 */
Debug_TrapWithRttHint("HardFault_Handler"); Debug_TrapWithRttHint("HardFault_Handler");
} }
/**
* @brief This function handles Memory management fault.
*/
void MemManage_Handler(void) void MemManage_Handler(void)
{ {
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
/* USER CODE END MemoryManagement_IRQn 0 */
Debug_TrapWithRttHint("MemManage_Handler"); Debug_TrapWithRttHint("MemManage_Handler");
} }
/**
* @brief This function handles Prefetch fault, memory access fault.
*/
void BusFault_Handler(void) void BusFault_Handler(void)
{ {
/* USER CODE BEGIN BusFault_IRQn 0 */
/* USER CODE END BusFault_IRQn 0 */
Debug_TrapWithRttHint("BusFault_Handler"); Debug_TrapWithRttHint("BusFault_Handler");
} }
/**
* @brief This function handles Undefined instruction or illegal state.
*/
void UsageFault_Handler(void) void UsageFault_Handler(void)
{ {
/* USER CODE BEGIN UsageFault_IRQn 0 */
/* USER CODE END UsageFault_IRQn 0 */
Debug_TrapWithRttHint("UsageFault_Handler"); Debug_TrapWithRttHint("UsageFault_Handler");
} }
/**
* @brief This function handles Debug monitor.
*/
void DebugMon_Handler(void) void DebugMon_Handler(void)
{ {
/* USER CODE BEGIN DebugMonitor_IRQn 0 */
/* USER CODE END DebugMonitor_IRQn 0 */
/* USER CODE BEGIN DebugMonitor_IRQn 1 */
/* USER CODE END DebugMonitor_IRQn 1 */
} }
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void) void SysTick_Handler(void)
{ {
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick(); HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
} }
/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f1xx.s). */
/******************************************************************************/
/**
* @brief This function handles DMA1 channel2 global interrupt.
*/
void DMA1_Channel2_IRQHandler(void) void DMA1_Channel2_IRQHandler(void)
{ {
/* USER CODE BEGIN DMA1_Channel2_IRQn 0 */
/* USER CODE END DMA1_Channel2_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart3_tx); HAL_DMA_IRQHandler(&hdma_usart3_tx);
/* USER CODE BEGIN DMA1_Channel2_IRQn 1 */
/* USER CODE END DMA1_Channel2_IRQn 1 */
} }
/**
* @brief This function handles DMA1 channel3 global interrupt.
*/
void DMA1_Channel3_IRQHandler(void) void DMA1_Channel3_IRQHandler(void)
{ {
/* USER CODE BEGIN DMA1_Channel3_IRQn 0 */
/* USER CODE END DMA1_Channel3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart3_rx); HAL_DMA_IRQHandler(&hdma_usart3_rx);
/* USER CODE BEGIN DMA1_Channel3_IRQn 1 */
/* USER CODE END DMA1_Channel3_IRQn 1 */
} }
/**
* @brief This function handles DMA1 channel4 global interrupt.
*/
void DMA1_Channel4_IRQHandler(void) void DMA1_Channel4_IRQHandler(void)
{ {
/* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
/* USER CODE END DMA1_Channel4_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart1_tx); HAL_DMA_IRQHandler(&hdma_usart1_tx);
/* USER CODE BEGIN DMA1_Channel4_IRQn 1 */
/* USER CODE END DMA1_Channel4_IRQn 1 */
} }
/**
* @brief This function handles DMA1 channel5 global interrupt.
*/
void DMA1_Channel5_IRQHandler(void) void DMA1_Channel5_IRQHandler(void)
{ {
/* USER CODE BEGIN DMA1_Channel5_IRQn 0 */
/* USER CODE END DMA1_Channel5_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart1_rx); HAL_DMA_IRQHandler(&hdma_usart1_rx);
/* USER CODE BEGIN DMA1_Channel5_IRQn 1 */
/* USER CODE END DMA1_Channel5_IRQn 1 */
} }
/**
* @brief This function handles DMA1 channel6 global interrupt.
*/
void DMA1_Channel6_IRQHandler(void) void DMA1_Channel6_IRQHandler(void)
{ {
/* USER CODE BEGIN DMA1_Channel6_IRQn 0 */
/* USER CODE END DMA1_Channel6_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart2_rx); HAL_DMA_IRQHandler(&hdma_usart2_rx);
/* USER CODE BEGIN DMA1_Channel6_IRQn 1 */
/* USER CODE END DMA1_Channel6_IRQn 1 */
} }
/**
* @brief This function handles DMA1 channel7 global interrupt.
*/
void DMA1_Channel7_IRQHandler(void) void DMA1_Channel7_IRQHandler(void)
{ {
/* USER CODE BEGIN DMA1_Channel7_IRQn 0 */
/* USER CODE END DMA1_Channel7_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart2_tx); HAL_DMA_IRQHandler(&hdma_usart2_tx);
/* USER CODE BEGIN DMA1_Channel7_IRQn 1 */
/* USER CODE END DMA1_Channel7_IRQn 1 */
} }
/**
* @brief This function handles TIM4 global interrupt.
*/
void TIM4_IRQHandler(void) void TIM4_IRQHandler(void)
{ {
/* USER CODE BEGIN TIM4_IRQn 0 */
/* USER CODE END TIM4_IRQn 0 */
HAL_TIM_IRQHandler(&htim4); HAL_TIM_IRQHandler(&htim4);
/* USER CODE BEGIN TIM4_IRQn 1 */
/* USER CODE END TIM4_IRQn 1 */
} }
/**
* @brief This function handles SPI1 global interrupt.
*/
void SPI1_IRQHandler(void) void SPI1_IRQHandler(void)
{ {
/* USER CODE BEGIN SPI1_IRQn 0 */
/* USER CODE END SPI1_IRQn 0 */
HAL_SPI_IRQHandler(&hspi1); HAL_SPI_IRQHandler(&hspi1);
/* USER CODE BEGIN SPI1_IRQn 1 */
/* USER CODE END SPI1_IRQn 1 */
} }
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void) void USART1_IRQHandler(void)
{ {
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1); HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
} }
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void) void USART2_IRQHandler(void)
{ {
/* USER CODE BEGIN USART2_IRQn 0 */
/* Handle IDLE interrupt for Server transparent transmission */
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE))
{ {
__HAL_UART_CLEAR_IDLEFLAG(&huart2); __HAL_UART_CLEAR_IDLEFLAG(&huart2);
uart_trans_idle_handler(UART_CHANNEL_SERVER); uart_trans_idle_handler(UART_CHANNEL_U0);
} }
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2); HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
} }
/**
* @brief This function handles USART3 global interrupt.
*/
void USART3_IRQHandler(void) void USART3_IRQHandler(void)
{ {
/* USER CODE BEGIN USART3_IRQn 0 */
/* Handle IDLE interrupt for Client transparent transmission */
if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE)) if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE))
{ {
__HAL_UART_CLEAR_IDLEFLAG(&huart3); __HAL_UART_CLEAR_IDLEFLAG(&huart3);
uart_trans_idle_handler(UART_CHANNEL_CLIENT); uart_trans_idle_handler(UART_CHANNEL_U1);
} }
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3); HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
} }
/* USER CODE BEGIN 1 */
extern volatile uint8_t g_uart1_rx_probe_byte;
/**
* @brief This function handles EXTI0 interrupt (CH390D INT pin).
*/
void EXTI0_IRQHandler(void) void EXTI0_IRQHandler(void)
{ {
/* Clear interrupt flag */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0)) if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0))
{ {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
/* Defer CH390 processing to main loop */
ethernetif_set_irq_pending(); ethernetif_set_irq_pending();
} }
} }
/**
* @brief HAL UART TX Complete callback
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{ {
if (huart == &huart2) if (huart == &huart2)
{ {
uart_trans_tx_cplt_handler(UART_CHANNEL_SERVER); uart_trans_tx_cplt_handler(UART_CHANNEL_U0);
} }
else if (huart == &huart3) else if (huart == &huart3)
{ {
uart_trans_tx_cplt_handler(UART_CHANNEL_CLIENT); uart_trans_tx_cplt_handler(UART_CHANNEL_U1);
} }
} }
/**
* @brief HAL UART RX Complete callback
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{ {
if (huart == &huart1) if (huart == &huart1)
{ {
config_uart_rx_byte(g_uart1_rx_probe_byte); config_uart_rx_byte(g_uart1_rx_probe_byte);
HAL_UART_Receive_IT(&huart1, (uint8_t *)&g_uart1_rx_probe_byte, 1u); (void)HAL_UART_Receive_IT(&huart1, (uint8_t *)&g_uart1_rx_probe_byte, 1u);
} }
else if (huart == &huart2) else if (huart == &huart2)
{ {
uart_trans_rx_cplt_handler(UART_CHANNEL_SERVER); uart_trans_rx_cplt_handler(UART_CHANNEL_U0);
} }
else if (huart == &huart3) else if (huart == &huart3)
{ {
uart_trans_rx_cplt_handler(UART_CHANNEL_CLIENT); uart_trans_rx_cplt_handler(UART_CHANNEL_U1);
} }
} }
/**
* @brief HAL UART RX Half Complete callback
*/
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{ {
if (huart == &huart2) if (huart == &huart2)
{ {
uart_trans_rx_half_cplt_handler(UART_CHANNEL_SERVER); uart_trans_rx_half_cplt_handler(UART_CHANNEL_U0);
} }
else if (huart == &huart3) else if (huart == &huart3)
{ {
uart_trans_rx_half_cplt_handler(UART_CHANNEL_CLIENT); uart_trans_rx_half_cplt_handler(UART_CHANNEL_U1);
} }
} }
/* USER CODE END 1 */
+58 -9
View File
@@ -12,6 +12,9 @@
#include "CH390.h" #include "CH390.h"
#include "CH390_Interface.h" #include "CH390_Interface.h"
#define CH390_PHY_BUSY_TIMEOUT_LOOPS 2000u
#define CH390_RX_READ_PTR_HIGH 0x0Cu
/** /**
* @name ch390_receive_packet * @name ch390_receive_packet
* @brief Receive packet * @brief Receive packet
@@ -119,10 +122,17 @@ void ch390_drop_packet(uint16_t len)
*/ */
uint16_t ch390_read_phy(uint8_t reg) uint16_t ch390_read_phy(uint8_t reg)
{ {
uint32_t timeout = CH390_PHY_BUSY_TIMEOUT_LOOPS;
ch390_write_reg(CH390_EPAR, CH390_PHY | reg); ch390_write_reg(CH390_EPAR, CH390_PHY | reg);
// Chose PHY, send read command // Chose PHY, send read command
ch390_write_reg(CH390_EPCR, EPCR_ERPRR | EPCR_EPOS); ch390_write_reg(CH390_EPCR, EPCR_ERPRR | EPCR_EPOS);
while(ch390_read_reg(CH390_EPCR) & 0x01); while ((ch390_read_reg(CH390_EPCR) & EPCR_ERRE) != 0u) {
if (timeout-- == 0u) {
ch390_write_reg(CH390_EPCR, 0x00);
return 0;
}
}
// Clear read command // Clear read command
ch390_write_reg(CH390_EPCR, 0x00); ch390_write_reg(CH390_EPCR, 0x00);
return (ch390_read_reg(CH390_EPDRH) << 8) | return (ch390_read_reg(CH390_EPDRH) << 8) |
@@ -137,12 +147,19 @@ uint16_t ch390_read_phy(uint8_t reg)
*/ */
void ch390_write_phy(uint8_t reg, uint16_t value) void ch390_write_phy(uint8_t reg, uint16_t value)
{ {
uint32_t timeout = CH390_PHY_BUSY_TIMEOUT_LOOPS;
ch390_write_reg(CH390_EPAR, CH390_PHY | reg); ch390_write_reg(CH390_EPAR, CH390_PHY | reg);
ch390_write_reg(CH390_EPDRL, (value & 0xff)); // Low byte ch390_write_reg(CH390_EPDRL, (value & 0xff)); // Low byte
ch390_write_reg(CH390_EPDRH, ((value >> 8) & 0xff)); // High byte ch390_write_reg(CH390_EPDRH, ((value >> 8) & 0xff)); // High byte
// Chose PHY, send write command // Chose PHY, send write command
ch390_write_reg(CH390_EPCR, 0x0A); ch390_write_reg(CH390_EPCR, 0x0A);
while(ch390_read_reg(CH390_EPCR) & 0x01); while ((ch390_read_reg(CH390_EPCR) & EPCR_ERRE) != 0u) {
if (timeout-- == 0u) {
ch390_write_reg(CH390_EPCR, 0x00);
return;
}
}
// Clear write command // Clear write command
ch390_write_reg(CH390_EPCR, 0x00); ch390_write_reg(CH390_EPCR, 0x00);
} }
@@ -172,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);
} }
/** /**
@@ -193,7 +236,13 @@ 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));
// Clear status // Clear status
ch390_write_reg(CH390_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END); ch390_write_reg(CH390_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
ch390_write_reg(CH390_ISR, 0xFF); // Clear interrupt status ch390_write_reg(CH390_ISR, 0xFF); // Clear interrupt status
@@ -203,9 +252,9 @@ 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 all interrupt and PAR // Enable pointer auto-return and only the interrupts used by the NO_SYS polling path.
ch390_write_reg(CH390_IMR, IMR_ALL); ch390_write_reg(CH390_IMR, (uint8_t)(IMR_PAR | IMR_PRI | IMR_LNKCHGI | IMR_ROOI | IMR_ROI));
// Enable RX // 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);
} }
+12
View File
@@ -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:
+65 -35
View File
@@ -15,6 +15,7 @@
#include "main.h" #include "main.h"
#include "CH390.h" #include "CH390.h"
#include "CH390_Interface.h" #include "CH390_Interface.h"
#include "SEGGER_RTT.h"
/* FreeRTOS includes */ /* FreeRTOS includes */
#ifdef USE_FREERTOS #ifdef USE_FREERTOS
@@ -47,11 +48,19 @@
#define CH390_INT_PORT GPIOB #define CH390_INT_PORT GPIOB
#define CH390_INT_PIN GPIO_PIN_0 #define CH390_INT_PIN GPIO_PIN_0
#define CH390_SCK_PORT GPIOA
#define CH390_SCK_PIN GPIO_PIN_5
#define CH390_MISO_PORT GPIOA
#define CH390_MISO_PIN GPIO_PIN_6
#define CH390_MOSI_PORT GPIOA
#define CH390_MOSI_PIN GPIO_PIN_7
/* External SPI handle from spi.c */ /* External SPI handle from spi.c */
extern SPI_HandleTypeDef hspi1; extern SPI_HandleTypeDef hspi1;
/* Timeout for SPI operations (ms) */ /* Timeout for SPI operations (ms) */
#define SPI_TIMEOUT 100 #define SPI_TIMEOUT 100
#define CH390_SPI_CHUNK_SIZE 64u
/*---------------------------------------------------------------------------- /*----------------------------------------------------------------------------
* Low-level GPIO operations * Low-level GPIO operations
@@ -65,6 +74,7 @@ static inline void ch390_cs(uint8_t state)
{ {
HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN,
state ? GPIO_PIN_SET : GPIO_PIN_RESET); state ? GPIO_PIN_SET : GPIO_PIN_RESET);
ch390_delay_us(2);
} }
/** /**
@@ -89,10 +99,42 @@ static inline void ch390_rst(uint8_t state)
static uint8_t ch390_spi_exchange_byte(uint8_t byte) static uint8_t ch390_spi_exchange_byte(uint8_t byte)
{ {
uint8_t rx_data = 0; uint8_t rx_data = 0;
HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, SPI_TIMEOUT); if (HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, SPI_TIMEOUT) != HAL_OK)
{
return 0;
}
return rx_data; return rx_data;
} }
static int ch390_spi_read_bytes(uint8_t *data, uint16_t length)
{
static const uint8_t dummy_tx[CH390_SPI_CHUNK_SIZE] = {0};
while (length > 0u)
{
uint16_t chunk = (length > CH390_SPI_CHUNK_SIZE) ? CH390_SPI_CHUNK_SIZE : length;
if (HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)dummy_tx, data, chunk, SPI_TIMEOUT) != HAL_OK)
{
return -1;
}
data += chunk;
length = (uint16_t)(length - chunk);
}
return 0;
}
static void ch390_spi_apply_mode(uint32_t polarity, uint32_t phase)
{
hspi1.Init.CLKPolarity = polarity;
hspi1.Init.CLKPhase = phase;
hspi1.Init.NSS = SPI_NSS_SOFT;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
/** /**
* @brief Read a dummy byte (send 0x00) * @brief Read a dummy byte (send 0x00)
* @return Received byte * @return Received byte
@@ -157,20 +199,9 @@ void ch390_interrupt_init(void)
void ch390_spi_init(void) void ch390_spi_init(void)
{ {
/* SPI1 is initialized by MX_SPI1_Init() in main.c */ /* SPI1 is initialized by MX_SPI1_Init() in main.c */
/* We need to ensure correct SPI mode for CH390: */ /* Reference CH390 SPI path uses mode 3. */
/* - CPOL = High (idle clock is high) */ ch390_spi_apply_mode(SPI_POLARITY_HIGH, SPI_PHASE_2EDGE);
/* - CPHA = 2Edge (data captured on second edge) */ SEGGER_RTT_WriteString(0, "CH390 SPI mode=3 (CPOL=1 CPHA=1)\r\n");
/* Reconfigure SPI for CH390 if needed */
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT; /* We control CS manually */
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
/* Handle error */
Error_Handler();
}
} }
/** /**
@@ -221,10 +252,11 @@ void ch390_delay_us(uint32_t time)
*/ */
void ch390_hardware_reset(void) void ch390_hardware_reset(void)
{ {
ch390_delay_us(10000); /* Short delay before reset */
ch390_rst(0); /* Assert reset (low) */ ch390_rst(0); /* Assert reset (low) */
ch390_delay_us(100); /* Hold reset for 100us (min 10us required) */ 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(10000); /* Wait 10ms for CH390 to initialize */ ch390_delay_us(500000); /* Allow EEPROM load, PHY, and SPI state to settle */
} }
/*---------------------------------------------------------------------------- /*----------------------------------------------------------------------------
@@ -256,8 +288,8 @@ uint8_t ch390_read_reg(uint8_t reg)
void ch390_write_reg(uint8_t reg, uint8_t value) void ch390_write_reg(uint8_t reg, uint8_t value)
{ {
ch390_cs(0); /* CS low - select */ ch390_cs(0); /* CS low - select */
ch390_spi_exchange_byte(reg | OPC_REG_W); /* Send write command */ (void)ch390_spi_exchange_byte(reg | OPC_REG_W);
ch390_spi_exchange_byte(value); /* Write register value */ (void)ch390_spi_exchange_byte(value);
ch390_cs(1); /* CS high - deselect */ ch390_cs(1); /* CS high - deselect */
} }
@@ -268,16 +300,15 @@ void ch390_write_reg(uint8_t reg, uint8_t value)
*/ */
void ch390_read_mem(uint8_t *data, int length) void ch390_read_mem(uint8_t *data, int length)
{ {
int i; if (data == NULL || length <= 0)
{
return;
}
ch390_cs(0); /* CS low - select */ ch390_cs(0); /* CS low - select */
ch390_spi_exchange_byte(OPC_MEM_READ); /* Send memory read command */ ch390_spi_exchange_byte(OPC_MEM_READ); /* Send memory read command */
/* Read data bytes */ (void)ch390_spi_read_bytes(data, (uint16_t)length);
for (i = 0; i < length; i++)
{
data[i] = ch390_spi_dummy_read();
}
ch390_cs(1); /* CS high - deselect */ ch390_cs(1); /* CS high - deselect */
} }
@@ -309,16 +340,15 @@ void ch390_read_mem_dma(uint8_t *data, int length)
*/ */
void ch390_write_mem(uint8_t *data, int length) void ch390_write_mem(uint8_t *data, int length)
{ {
int i; if (data == NULL || length <= 0)
{
return;
}
ch390_cs(0); /* CS low - select */ ch390_cs(0); /* CS low - select */
ch390_spi_exchange_byte(OPC_MEM_WRITE); /* Send memory write command */ ch390_spi_exchange_byte(OPC_MEM_WRITE); /* Send memory write command */
/* Write data bytes */ (void)HAL_SPI_Transmit(&hspi1, data, (uint16_t)length, SPI_TIMEOUT);
for (i = 0; i < length; i++)
{
ch390_spi_exchange_byte(data[i]);
}
ch390_cs(1); /* CS high - deselect */ ch390_cs(1); /* CS high - deselect */
} }
+736
View File
@@ -0,0 +1,736 @@
#include "ch390_runtime.h"
#include "CH390.h"
#include "CH390_Interface.h"
#include "SEGGER_RTT.h"
#include "ethernetif.h"
#include "stm32f1xx_hal.h"
#include "lwip/etharp.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include <string.h>
static void ch390_runtime_dispatch_frame(struct netif *netif, struct pbuf *p)
{
if ((p != NULL) && (netif->input(p, netif) != ERR_OK)) {
pbuf_free(p);
}
}
static uint8_t ch390_runtime_drain_rx(struct netif *netif, uint8_t max_frames)
{
struct pbuf *p;
uint8_t drained = 0u;
uint8_t rx_ready;
while (drained < max_frames) {
p = ch390_runtime_input_frame(netif);
if (p == NULL) {
ch390_read_reg(CH390_MRCMDX);
rx_ready = ch390_read_reg(CH390_MRCMDX);
if ((rx_ready & CH390_PKT_RDY) == 0u) {
break;
}
drained++;
continue;
}
ch390_runtime_dispatch_frame(netif, p);
drained++;
}
return drained;
}
static volatile uint8_t g_ch390_irq_pending;
static uint8_t g_ch390_ready;
static ch390_diag_t g_diag;
static uint8_t g_tx_consecutive_timeout;
static uint8_t g_chip_reset_count;
static uint8_t g_link_restart_pending;
#define TX_BUSY_WAIT_TIMEOUT_MS 10u
#define TX_TIMEOUT_RESET_THRESHOLD 6u
#define HEALTH_FAIL_THRESHOLD 3u
#define RESTART_PENDING_FLAG 0x01u
#define HEALTH_FAIL_SHIFT 4u
#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 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)
{
return (uint8_t)(g_link_restart_pending & RESTART_PENDING_FLAG);
}
static void ch390_runtime_set_restart_pending(void)
{
g_link_restart_pending = (uint8_t)(g_link_restart_pending | RESTART_PENDING_FLAG);
}
static void ch390_runtime_clear_restart_pending(void)
{
g_link_restart_pending = (uint8_t)(g_link_restart_pending & (uint8_t)(~RESTART_PENDING_FLAG));
}
static uint8_t ch390_runtime_get_health_fail_count(void)
{
return (uint8_t)((g_link_restart_pending & HEALTH_FAIL_MASK) >> HEALTH_FAIL_SHIFT);
}
static void ch390_runtime_set_health_fail_count(uint8_t count)
{
g_link_restart_pending = (uint8_t)((g_link_restart_pending & (uint8_t)(~HEALTH_FAIL_MASK)) |
(uint8_t)((count << HEALTH_FAIL_SHIFT) & HEALTH_FAIL_MASK));
}
static uint8_t ch390_runtime_probe_identity(void)
{
g_diag.vendor_id = ch390_get_vendor_id();
g_diag.product_id = ch390_get_product_id();
g_diag.revision = ch390_get_revision();
g_diag.phy_bmcr = ch390_read_phy(CH390_PHY_BMCR);
g_diag.phy_bmsr = ch390_read_phy(CH390_PHY_BMSR);
g_diag.phy_id1 = ch390_read_phy(CH390_PHY_PHYID1);
g_diag.phy_id2 = ch390_read_phy(CH390_PHY_PHYID2);
g_diag.phy_anar = ch390_read_phy(CH390_PHY_ANAR);
g_diag.phy_anlpar = ch390_read_phy(CH390_PHY_ANLPAR);
g_diag.phy_aner = ch390_read_phy(CH390_PHY_ANER);
g_diag.nsr = ch390_read_reg(CH390_NSR);
g_diag.ncr = ch390_read_reg(CH390_NCR);
g_diag.rcr = ch390_read_reg(CH390_RCR);
g_diag.imr = ch390_read_reg(CH390_IMR);
g_diag.intcr = ch390_read_reg(CH390_INTCR);
g_diag.gpr = ch390_read_reg(CH390_GPR);
g_diag.isr = ch390_read_reg(CH390_ISR);
g_diag.phy_speed_10m = 0u;
g_diag.phy_full_duplex = 0u;
g_diag.link_up = (uint8_t)0u;
g_diag.id_valid = (uint8_t)((g_diag.vendor_id != 0x0000u) &&
(g_diag.vendor_id != 0xFFFFu) &&
(g_diag.product_id != 0x0000u) &&
(g_diag.product_id != 0xFFFFu));
return g_diag.id_valid;
}
static void ch390_runtime_prepare_netif(struct netif *netif)
{
struct ethernetif *ethernetif;
if (netif == NULL) {
return;
}
netif->hwaddr_len = ETHARP_HWADDR_LEN;
netif->mtu = 1500;
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET;
ethernetif = (struct ethernetif *)netif->state;
if (ethernetif != NULL) {
ethernetif->rx_len = 0u;
ethernetif->rx_status = 0u;
}
}
static void ch390_runtime_sync_mac(struct netif *netif)
{
if (netif == NULL) {
return;
}
if (ch390_mac_address_valid(netif->hwaddr)) {
ch390_set_mac_address(netif->hwaddr);
}
ch390_get_mac(netif->hwaddr);
}
static void ch390_runtime_refresh_diag(void)
{
uint8_t id_valid = ch390_runtime_probe_identity();
g_diag.int_pin = (uint8_t)ch390_get_int_pin();
if (id_valid != 0u) {
g_diag.phy_speed_10m = (uint8_t)ch390_get_phy_speed();
g_diag.phy_full_duplex = (uint8_t)ch390_get_duplex_mode();
g_diag.link_up = (uint8_t)ch390_get_link_status();
}
}
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 ethernetif *ethernetif = (struct ethernetif *)netif->state;
struct pbuf *p = NULL;
uint16_t len;
uint16_t frame_len;
uint16_t prefix_len;
uint8_t rcr;
uint8_t rx_ready;
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);
rx_ready = ch390_read_reg(CH390_MRCMDX);
if (rx_ready & CH390_PKT_ERR) {
rcr = ch390_read_reg(CH390_RCR);
ch390_write_reg(CH390_RCR, (uint8_t)(rcr & (uint8_t)(~RCR_RXEN)));
ch390_write_reg(CH390_MPTRCR, 0x01u);
ch390_write_reg(CH390_MRRH, 0x0Cu);
ch390_delay_us(1000u);
ch390_write_reg(CH390_RCR, (uint8_t)(rcr | RCR_RXEN));
ethernetif->rx_len = 0u;
LINK_STATS_INC(link.drop);
g_diag.rx_packets_drop++;
return NULL;
}
if ((rx_ready & CH390_PKT_RDY) == 0u) {
ethernetif->rx_len = 0u;
return NULL;
}
g_diag.rx_ready_hits++;
ch390_read_mem(rx_header, 4);
ethernetif->rx_status = rx_header[1];
frame_len = (uint16_t)((uint16_t)rx_header[2] | ((uint16_t)rx_header[3] << 8));
if ((ethernetif->rx_status & 0x3Fu) != 0u || frame_len == 0u || frame_len > CH390_PKT_MAX) {
ethernetif->rx_len = 0u;
ch390_drop_packet(frame_len);
LINK_STATS_INC(link.drop);
g_diag.rx_packets_drop++;
return NULL;
}
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;
#if ETH_PAD_SIZE
len += ETH_PAD_SIZE;
#endif
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
ch390_runtime_copy_prefix_to_pbuf(p, frame_prefix, prefix_len);
ch390_runtime_read_remaining_to_pbuf(p, prefix_len);
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE);
#endif
LINK_STATS_INC(link.recv);
g_diag.rx_packets_ok++;
g_diag.last_frame_len = frame_len;
g_diag.last_payload_len = p->tot_len;
} else {
ch390_drop_packet((uint16_t)(frame_len - prefix_len));
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
g_diag.rx_packets_drop++;
g_diag.rx_pbuf_alloc_failed++;
}
return p;
}
static bool ch390_mac_address_valid(const uint8_t *mac)
{
if (mac == NULL) {
return false;
}
for (uint8_t i = 0; i < ETHARP_HWADDR_LEN; ++i) {
if (mac[i] == 0u) {
return false;
}
}
return true;
}
void ch390_runtime_init(struct netif *netif, const uint8_t *mac)
{
SEGGER_RTT_WriteString(0, "ETH init: gpio\r\n");
ch390_gpio_init();
SEGGER_RTT_WriteString(0, "ETH init: spi\r\n");
ch390_spi_init();
SEGGER_RTT_WriteString(0, "ETH init: deep reset\r\n");
g_ch390_ready = (uint8_t)ch390_runtime_recover_chip(netif, mac);
if (g_ch390_ready == 0u) {
ch390_runtime_prepare_netif(netif);
netif_set_link_down(netif);
SEGGER_RTT_WriteString(0, "ETH init: invalid chip id\r\n");
return;
}
SEGGER_RTT_WriteString(0, "ETH init: mac\r\n");
if (ch390_mac_address_valid(mac)) {
ch390_runtime_sync_mac(netif);
}
else {
if (mac != NULL) {
ch390_get_mac((uint8_t *)mac);
SEGGER_RTT_printf(0, "ETH init: invalid MAC in config, using hardware MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
else {
SEGGER_RTT_WriteString(0, "ETH init: no MAC in config\r\n");
}
}
SEGGER_RTT_WriteString(0, "ETH init: getmac\r\n");
ch390_runtime_prepare_netif(netif);
ch390_get_mac(netif->hwaddr);
ch390_runtime_refresh_diag();
g_ch390_ready = g_diag.id_valid;
SEGGER_RTT_WriteString(0, "ETH init: irq\r\n");
ch390_interrupt_init();
SEGGER_RTT_WriteString(0, "ETH init: done\r\n");
}
void ch390_runtime_set_irq_pending(void)
{
g_ch390_irq_pending = 1u;
}
uint8_t ch390_runtime_is_irq_pending(void)
{
return g_ch390_irq_pending;
}
void ch390_runtime_poll(struct netif *netif)
{
uint8_t int_status;
uint8_t rx_ready;
uint8_t rx_budget;
uint8_t rx_hint;
if (!g_ch390_ready) {
return;
}
g_diag.rx_poll_calls++;
rx_budget = 1u;
rx_hint = 0u;
if ((g_ch390_irq_pending != 0u) || (ch390_get_int_pin() == GPIO_PIN_RESET)) {
g_ch390_irq_pending = 0u;
int_status = ch390_get_int_status();
if ((int_status & ISR_LNKCHG) != 0u) {
ch390_runtime_check_link(netif);
}
if ((int_status & ISR_ROS) != 0u) {
LINK_STATS_INC(link.err);
}
if ((int_status & (ISR_PR | ISR_ROS | ISR_ROO)) != 0u) {
rx_hint = 1u;
rx_budget = 8u;
}
}
ch390_read_reg(CH390_MRCMDX);
rx_ready = ch390_read_reg(CH390_MRCMDX);
if ((rx_ready & CH390_PKT_RDY) != 0u) {
rx_hint = 1u;
if (rx_budget < 4u) {
rx_budget = 4u;
}
}
if (rx_hint != 0u) {
(void)ch390_runtime_drain_rx(netif, rx_budget);
}
}
void ch390_runtime_check_link(struct netif *netif)
{
uint8_t link_up;
static uint8_t s_last_reported = 0xFFu;
if (!g_ch390_ready) {
netif_set_link_down(netif);
return;
}
if (ch390_runtime_is_restart_pending() != 0u) {
netif_set_link_down(netif);
ch390_runtime_clear_restart_pending();
SEGGER_RTT_WriteString(0, "ETH restart pending: hold link down for app recycle\r\n");
return;
}
ch390_runtime_refresh_diag();
link_up = (uint8_t)ch390_get_link_status();
if (link_up != s_last_reported) {
SEGGER_RTT_printf(0,
"ETH link %s nsr=0x%02X bmsr=0x%04X anlpar=0x%04X\r\n",
link_up ? "up" : "down",
g_diag.nsr,
g_diag.phy_bmsr,
g_diag.phy_anlpar);
s_last_reported = link_up;
}
if (link_up) {
if (!netif_is_link_up(netif)) {
netif_set_link_up(netif);
}
} else if (netif_is_link_up(netif)) {
netif_set_link_down(netif);
}
}
err_t ch390_runtime_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
uint32_t start_tick;
if (!g_ch390_ready) {
LINK_STATS_INC(link.drop);
return ERR_IF;
}
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
start_tick = HAL_GetTick();
while (ch390_read_reg(CH390_TCR) & TCR_TXREQ) {
if ((HAL_GetTick() - start_tick) > TX_BUSY_WAIT_TIMEOUT_MS) {
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE);
#endif
LINK_STATS_INC(link.drop);
g_diag.tx_packets_timeout++;
if (g_tx_consecutive_timeout < 0xFFu) {
g_tx_consecutive_timeout++;
}
if (g_tx_consecutive_timeout >= TX_TIMEOUT_RESET_THRESHOLD) {
(void)ch390_runtime_emergency_reset(netif);
}
return ERR_TIMEOUT;
}
}
g_tx_consecutive_timeout = 0u;
for (q = p; q != NULL; q = q->next) {
ch390_write_mem((uint8_t *)q->payload, q->len);
}
ch390_write_reg(CH390_TXPLL, p->tot_len & 0xFFu);
ch390_write_reg(CH390_TXPLH, (p->tot_len >> 8) & 0xFFu);
ch390_send_request();
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE);
#endif
LINK_STATS_INC(link.xmit);
g_diag.tx_packets_ok++;
return ERR_OK;
}
void ch390_runtime_get_diag(ch390_diag_t *diag)
{
if (diag != NULL) {
ch390_runtime_refresh_diag();
*diag = g_diag;
}
}
bool ch390_runtime_is_ready(void)
{
return g_ch390_ready != 0u;
}
bool ch390_runtime_emergency_reset(struct netif *netif)
{
SEGGER_RTT_printf(0, "ETH emergency reset (tx_timeout=%u resets=%u)\r\n",
g_tx_consecutive_timeout, g_chip_reset_count);
if (netif != NULL) {
netif_set_link_down(netif);
}
if (g_chip_reset_count < 0xFFu) {
g_chip_reset_count++;
}
g_tx_consecutive_timeout = 0u;
(void)ch390_runtime_recover_chip(netif, (netif != NULL) ? netif->hwaddr : NULL);
if (g_ch390_ready == 0u) {
SEGGER_RTT_WriteString(0, "ETH emergency reset: chip not responding\r\n");
return false;
}
ch390_runtime_set_health_fail_count(0u);
ch390_runtime_set_restart_pending();
SEGGER_RTT_WriteString(0, "ETH emergency reset: OK\r\n");
return true;
}
void ch390_runtime_health_check(struct netif *netif)
{
uint16_t vid;
uint8_t fail_count;
if (!g_ch390_ready) {
SEGGER_RTT_WriteString(0, "ETH health: chip not ready, attempting reset\r\n");
(void)ch390_runtime_emergency_reset(netif);
return;
}
/* Verify chip is still responding by reading vendor ID */
vid = ch390_get_vendor_id();
if (vid == 0x0000u || vid == 0xFFFFu) {
fail_count = ch390_runtime_get_health_fail_count();
if (fail_count < 0x0Fu) {
fail_count++;
}
ch390_runtime_set_health_fail_count(fail_count);
if (fail_count >= HEALTH_FAIL_THRESHOLD) {
SEGGER_RTT_printf(0, "ETH health: invalid VID=0x%04X streak=%u, attempting reset\r\n",
vid,
fail_count);
ch390_runtime_set_health_fail_count(0u);
(void)ch390_runtime_emergency_reset(netif);
}
} else {
ch390_runtime_set_health_fail_count(0u);
}
}
uint8_t ch390_runtime_get_reset_count(void)
{
return g_chip_reset_count;
}
+77
View File
@@ -0,0 +1,77 @@
#ifndef __CH390_RUNTIME_H__
#define __CH390_RUNTIME_H__
#include <stdbool.h>
#include <stdint.h>
#include "lwip/err.h"
struct netif;
struct pbuf;
typedef struct {
uint16_t vendor_id;
uint16_t product_id;
uint8_t revision;
uint16_t phy_bmcr;
uint16_t phy_bmsr;
uint16_t phy_id1;
uint16_t phy_id2;
uint16_t phy_anar;
uint16_t phy_anlpar;
uint16_t phy_aner;
uint8_t nsr;
uint8_t ncr;
uint8_t rcr;
uint8_t imr;
uint8_t intcr;
uint8_t gpr;
uint8_t isr;
uint8_t int_pin;
uint8_t phy_speed_10m;
uint8_t phy_full_duplex;
uint8_t link_up;
uint8_t id_valid;
uint32_t rx_poll_calls;
uint32_t rx_ready_hits;
uint32_t rx_packets_ok;
uint32_t rx_packets_drop;
uint32_t tx_packets_ok;
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_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_unicast_self_frames;
uint32_t rx_broadcast_frames;
uint32_t rx_multicast_frames;
uint16_t last_frame_len;
uint16_t last_payload_len;
uint16_t last_eth_type;
} ch390_diag_t;
void ch390_runtime_init(struct netif *netif, const uint8_t *mac);
struct pbuf *ch390_runtime_input_frame(struct netif *netif);
void ch390_runtime_set_irq_pending(void);
uint8_t ch390_runtime_is_irq_pending(void);
void ch390_runtime_poll(struct netif *netif);
void ch390_runtime_check_link(struct netif *netif);
err_t ch390_runtime_output(struct netif *netif, struct pbuf *p);
void ch390_runtime_get_diag(ch390_diag_t *diag);
bool ch390_runtime_is_ready(void);
bool ch390_runtime_emergency_reset(struct netif *netif);
void ch390_runtime_health_check(struct netif *netif);
uint8_t ch390_runtime_get_reset_count(void);
#endif
-1
View File
@@ -645,7 +645,6 @@ etharp_input(struct pbuf *p, struct netif *netif)
/* these are aligned properly, whereas the ARP header fields might not be */ /* these are aligned properly, whereas the ARP header fields might not be */
ip4_addr_t sipaddr, dipaddr; ip4_addr_t sipaddr, dipaddr;
u8_t for_us, from_us; u8_t for_us, from_us;
LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("netif != NULL", (netif != NULL), return;); LWIP_ERROR("netif != NULL", (netif != NULL), return;);
-1
View File
@@ -87,7 +87,6 @@ icmp_input(struct pbuf *p, struct netif *inp)
const struct ip_hdr *iphdr_in; const struct ip_hdr *iphdr_in;
u16_t hlen; u16_t hlen;
const ip4_addr_t *src; const ip4_addr_t *src;
ICMP_STATS_INC(icmp.recv); ICMP_STATS_INC(icmp.recv);
MIB2_STATS_INC(mib2.icmpinmsgs); MIB2_STATS_INC(mib2.icmpinmsgs);
+13 -180
View File
@@ -18,175 +18,51 @@
#include "CH390.h" #include "CH390.h"
#include "CH390_Interface.h" #include "CH390_Interface.h"
#include "ch390_runtime.h"
#include "config.h" #include "config.h"
#include "stm32f1xx_hal.h" #include "stm32f1xx_hal.h"
#include "SEGGER_RTT.h"
#define IFNAME0 'e' #define IFNAME0 'e'
#define IFNAME1 'n' #define IFNAME1 'n'
struct netif ch390_netif; struct netif ch390_netif;
static volatile uint8_t g_ch390_irq_pending;
static void ethernetif_unlock(uint32_t primask);
static uint32_t ethernetif_lock(void)
{
uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
sys_prot_t sys_arch_protect(void) sys_prot_t sys_arch_protect(void)
{ {
return (sys_prot_t)ethernetif_lock(); __disable_irq();
return 0u;
} }
void sys_arch_unprotect(sys_prot_t pval) void sys_arch_unprotect(sys_prot_t pval)
{ {
ethernetif_unlock((uint32_t)pval); LWIP_UNUSED_ARG(pval);
} __enable_irq();
static void ethernetif_unlock(uint32_t primask)
{
if ((primask & 1u) == 0u) {
__enable_irq();
}
} }
void ethernetif_set_irq_pending(void) void ethernetif_set_irq_pending(void)
{ {
g_ch390_irq_pending = 1u; ch390_runtime_set_irq_pending();
} }
uint8_t ethernetif_is_irq_pending(void) uint8_t ethernetif_is_irq_pending(void)
{ {
return g_ch390_irq_pending; return ch390_runtime_is_irq_pending();
} }
static void low_level_init(struct netif *netif) static void low_level_init(struct netif *netif)
{ {
struct ethernetif *ethernetif = netif->state; ch390_runtime_init(netif, config_get()->net.mac);
ch390_gpio_init();
ch390_spi_init();
ch390_hardware_reset();
ch390_default_config();
ch390_set_mac_address((uint8_t *)config_get()->mac);
netif->hwaddr_len = ETHARP_HWADDR_LEN;
ch390_get_mac(netif->hwaddr);
netif->mtu = 1500;
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
ethernetif->rx_len = 0u;
ethernetif->rx_status = 0u;
ch390_interrupt_init();
} }
static err_t low_level_output(struct netif *netif, struct pbuf *p) static err_t low_level_output(struct netif *netif, struct pbuf *p)
{ {
struct pbuf *q; return ch390_runtime_output(netif, p);
uint32_t primask;
uint32_t start_tick;
LWIP_UNUSED_ARG(netif);
primask = ethernetif_lock();
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
start_tick = HAL_GetTick();
while (ch390_read_reg(CH390_TCR) & TCR_TXREQ) {
if ((HAL_GetTick() - start_tick) > 10u) {
ethernetif_unlock(primask);
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE);
#endif
LINK_STATS_INC(link.drop);
return ERR_TIMEOUT;
}
}
for (q = p; q != NULL; q = q->next) {
ch390_write_mem(q->payload, q->len);
}
ch390_write_reg(CH390_TXPLL, p->tot_len & 0xFFu);
ch390_write_reg(CH390_TXPLH, (p->tot_len >> 8) & 0xFFu);
ch390_send_request();
ethernetif_unlock(primask);
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE);
#endif
LINK_STATS_INC(link.xmit);
return ERR_OK;
} }
static struct pbuf *low_level_input(struct netif *netif) static struct pbuf *low_level_input(struct netif *netif)
{ {
struct ethernetif *ethernetif = netif->state; return ch390_runtime_input_frame(netif);
struct pbuf *p = NULL;
struct pbuf *q;
uint16_t len;
uint8_t rx_ready;
uint8_t rx_header[4];
uint32_t primask;
primask = ethernetif_lock();
ch390_read_reg(CH390_MRCMDX);
rx_ready = ch390_read_reg(CH390_MRCMDX);
if (rx_ready & CH390_PKT_ERR) {
ch390_write_reg(CH390_RCR, 0u);
ch390_write_reg(CH390_MPTRCR, 0x01u);
ch390_write_reg(CH390_MRRH, 0x0Cu);
ch390_delay_us(1000u);
ch390_write_reg(CH390_RCR, RCR_RXEN | RCR_DIS_CRC);
ethernetif->rx_len = 0u;
LINK_STATS_INC(link.drop);
ethernetif_unlock(primask);
return NULL;
}
if ((rx_ready & CH390_PKT_RDY) == 0u) {
ethernetif->rx_len = 0u;
ethernetif_unlock(primask);
return NULL;
}
ch390_read_mem(rx_header, 4);
ethernetif->rx_status = rx_header[1];
ethernetif->rx_len = (uint16_t)(((uint16_t)rx_header[2] | ((uint16_t)rx_header[3] << 8)) - 4u);
len = ethernetif->rx_len;
#if ETH_PAD_SIZE
len += ETH_PAD_SIZE;
#endif
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
for (q = p; q != NULL; q = q->next) {
ch390_read_mem(q->payload, q->len);
}
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE);
#endif
ch390_drop_packet(4u);
LINK_STATS_INC(link.recv);
} else {
ch390_drop_packet((uint16_t)(ethernetif->rx_len + 4u));
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
}
ethernetif_unlock(primask);
return p;
} }
void ethernetif_input(struct netif *netif) void ethernetif_input(struct netif *netif)
@@ -237,55 +113,12 @@ void lwip_netif_init(const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const
void ethernetif_check_link(void) void ethernetif_check_link(void)
{ {
uint8_t link_up; ch390_runtime_check_link(&ch390_netif);
uint32_t primask = ethernetif_lock();
link_up = (uint8_t)ch390_get_link_status();
ethernetif_unlock(primask);
if (link_up) {
if (!netif_is_link_up(&ch390_netif)) {
netif_set_link_up(&ch390_netif);
}
} else if (netif_is_link_up(&ch390_netif)) {
netif_set_link_down(&ch390_netif);
}
} }
void ethernetif_poll(void) void ethernetif_poll(void)
{ {
uint8_t int_status; ch390_runtime_poll(&ch390_netif);
uint32_t primask;
if (g_ch390_irq_pending == 0u) {
return;
}
primask = ethernetif_lock();
int_status = ch390_read_reg(CH390_ISR);
ch390_write_reg(CH390_ISR, int_status);
g_ch390_irq_pending = 0u;
ethernetif_unlock(primask);
if ((int_status & ISR_LNKCHG) != 0u) {
ethernetif_check_link();
}
if ((int_status & ISR_ROS) != 0u) {
LINK_STATS_INC(link.err);
}
if ((int_status & ISR_PR) != 0u) {
while (1) {
struct pbuf *p = low_level_input(&ch390_netif);
if (p == NULL) {
break;
}
if (ch390_netif.input(p, &ch390_netif) != ERR_OK) {
pbuf_free(p);
}
}
}
} }
u32_t sys_now(void) u32_t sys_now(void)
+1
View File
@@ -8,6 +8,7 @@
#include "lwip/err.h" #include "lwip/err.h"
#include "lwip/netif.h" #include "lwip/netif.h"
#include "ch390_runtime.h"
struct ethernetif { struct ethernetif {
uint16_t rx_len; uint16_t rx_len;
-195
View File
@@ -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/
========================================
+436 -79
View File
@@ -45,7 +45,7 @@
<PageWidth>79</PageWidth> <PageWidth>79</PageWidth>
<PageLength>66</PageLength> <PageLength>66</PageLength>
<TabStop>8</TabStop> <TabStop>8</TabStop>
<ListingPath></ListingPath> <ListingPath>.\TCP2UART\</ListingPath>
</OPTLEX> </OPTLEX>
<ListingPage> <ListingPage>
<CreateCListing>1</CreateCListing> <CreateCListing>1</CreateCListing>
@@ -103,7 +103,7 @@
<bEvRecOn>1</bEvRecOn> <bEvRecOn>1</bEvRecOn>
<bSchkAxf>0</bSchkAxf> <bSchkAxf>0</bSchkAxf>
<bTchkAxf>0</bTchkAxf> <bTchkAxf>0</bTchkAxf>
<nTsel>6</nTsel> <nTsel>3</nTsel>
<sDll></sDll> <sDll></sDll>
<sDllPa></sDllPa> <sDllPa></sDllPa>
<sDlgDll></sDlgDll> <sDlgDll></sDlgDll>
@@ -114,9 +114,34 @@
<tDlgDll></tDlgDll> <tDlgDll></tDlgDll>
<tDlgPa></tDlgPa> <tDlgPa></tDlgPa>
<tIfile></tIfile> <tIfile></tIfile>
<pMon>STLink\ST-LINKIII-KEIL_SWO.dll</pMon> <pMon>BIN\CMSIS_AGDI.dll</pMon>
</DebugOpt> </DebugOpt>
<TargetDriverDllRegistry> <TargetDriverDllRegistry>
<SetRegEntry>
<Number>0</Number>
<Key>ARMRTXEVENTFLAGS</Key>
<Name>-L70 -Z18 -C0 -M0 -T1</Name>
</SetRegEntry>
<SetRegEntry>
<Number>0</Number>
<Key>DLGTARM</Key>
<Name>(1010=-1,-1,-1,-1,0)(1007=-1,-1,-1,-1,0)(1008=-1,-1,-1,-1,0)(1009=-1,-1,-1,-1,0)</Name>
</SetRegEntry>
<SetRegEntry>
<Number>0</Number>
<Key>ARMDBGFLAGS</Key>
<Name></Name>
</SetRegEntry>
<SetRegEntry>
<Number>0</Number>
<Key>DLGUARM</Key>
<Name></Name>
</SetRegEntry>
<SetRegEntry>
<Number>0</Number>
<Key>CMSIS_AGDI</Key>
<Name>-X"Any" -UAny -O206 -S8 -C0 -P00000000 -N00("ARM CoreSight SW-DP") -D00(1BA01477) -L00(0) -TO65554 -TC10000000 -TT10000000 -TP20 -TDS8007 -TDT0 -TDC1F -TIEFFFFFFFF -TIP8 -FO15 -FD20000000 -FC1000 -FN1 -FF0STM32F10x_128.FLM -FS08000000 -FL020000 -FP0($$Device:STM32F103R8$Flash\STM32F10x_128.FLM)</Name>
</SetRegEntry>
<SetRegEntry> <SetRegEntry>
<Number>0</Number> <Number>0</Number>
<Key>UL2CM3</Key> <Key>UL2CM3</Key>
@@ -194,8 +219,8 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>startup_stm32f103xe.s</PathWithFileName> <PathWithFileName>startup_stm32f103xb.s</PathWithFileName>
<FilenameWithoutPath>startup_stm32f103xe.s</FilenameWithoutPath> <FilenameWithoutPath>startup_stm32f103xb.s</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
@@ -238,8 +263,8 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Core/Src/freertos.c</PathWithFileName> <PathWithFileName>../Core/Src/dma.c</PathWithFileName>
<FilenameWithoutPath>freertos.c</FilenameWithoutPath> <FilenameWithoutPath>dma.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
@@ -250,8 +275,8 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Core/Src/dma.c</PathWithFileName> <PathWithFileName>../Core/Src/iwdg.c</PathWithFileName>
<FilenameWithoutPath>dma.c</FilenameWithoutPath> <FilenameWithoutPath>iwdg.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
@@ -262,8 +287,8 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Core/Src/iwdg.c</PathWithFileName> <PathWithFileName>../Core/Src/tim.c</PathWithFileName>
<FilenameWithoutPath>iwdg.c</FilenameWithoutPath> <FilenameWithoutPath>tim.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
@@ -342,6 +367,18 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_iwdg.c</PathWithFileName>
<FilenameWithoutPath>stm32f1xx_hal_iwdg.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>3</GroupNumber>
<FileNumber>13</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c</PathWithFileName> <PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c</PathWithFileName>
<FilenameWithoutPath>stm32f1xx_hal.c</FilenameWithoutPath> <FilenameWithoutPath>stm32f1xx_hal.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
@@ -349,7 +386,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>13</FileNumber> <FileNumber>14</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -361,7 +398,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>14</FileNumber> <FileNumber>15</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -373,7 +410,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>15</FileNumber> <FileNumber>16</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -385,7 +422,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>16</FileNumber> <FileNumber>17</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -397,7 +434,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>17</FileNumber> <FileNumber>18</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -409,7 +446,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>18</FileNumber> <FileNumber>19</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -421,7 +458,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>19</FileNumber> <FileNumber>20</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -433,7 +470,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>20</FileNumber> <FileNumber>21</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -445,7 +482,7 @@
</File> </File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>21</FileNumber> <FileNumber>22</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -455,18 +492,6 @@
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
<File>
<GroupNumber>3</GroupNumber>
<FileNumber>22</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_iwdg.c</PathWithFileName>
<FilenameWithoutPath>stm32f1xx_hal_iwdg.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File> <File>
<GroupNumber>3</GroupNumber> <GroupNumber>3</GroupNumber>
<FileNumber>23</FileNumber> <FileNumber>23</FileNumber>
@@ -486,6 +511,30 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim.c</PathWithFileName>
<FilenameWithoutPath>stm32f1xx_hal_tim.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>3</GroupNumber>
<FileNumber>25</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim_ex.c</PathWithFileName>
<FilenameWithoutPath>stm32f1xx_hal_tim_ex.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>3</GroupNumber>
<FileNumber>26</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_uart.c</PathWithFileName> <PathWithFileName>../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_uart.c</PathWithFileName>
<FilenameWithoutPath>stm32f1xx_hal_uart.c</FilenameWithoutPath> <FilenameWithoutPath>stm32f1xx_hal_uart.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
@@ -501,7 +550,7 @@
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<File> <File>
<GroupNumber>4</GroupNumber> <GroupNumber>4</GroupNumber>
<FileNumber>25</FileNumber> <FileNumber>27</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
@@ -514,35 +563,11 @@
</Group> </Group>
<Group> <Group>
<GroupName>Middlewares/FreeRTOS</GroupName> <GroupName>Drivers/CH390</GroupName>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel> <cbSel>0</cbSel>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<File>
<GroupNumber>5</GroupNumber>
<FileNumber>26</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/croutine.c</PathWithFileName>
<FilenameWithoutPath>croutine.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>5</GroupNumber>
<FileNumber>27</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/event_groups.c</PathWithFileName>
<FilenameWithoutPath>event_groups.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File> <File>
<GroupNumber>5</GroupNumber> <GroupNumber>5</GroupNumber>
<FileNumber>28</FileNumber> <FileNumber>28</FileNumber>
@@ -550,8 +575,8 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/list.c</PathWithFileName> <PathWithFileName>..\Drivers\CH390\CH390.c</PathWithFileName>
<FilenameWithoutPath>list.c</FilenameWithoutPath> <FilenameWithoutPath>CH390.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
@@ -562,8 +587,8 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/queue.c</PathWithFileName> <PathWithFileName>..\Drivers\CH390\CH390_Interface.c</PathWithFileName>
<FilenameWithoutPath>queue.c</FilenameWithoutPath> <FilenameWithoutPath>CH390_Interface.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
@@ -574,68 +599,400 @@
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/stream_buffer.c</PathWithFileName> <PathWithFileName>..\Drivers\CH390\ch390_runtime.c</PathWithFileName>
<FilenameWithoutPath>stream_buffer.c</FilenameWithoutPath> <FilenameWithoutPath>ch390_runtime.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
</Group>
<Group>
<GroupName>Drivers/LwIP/core</GroupName>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>
<File> <File>
<GroupNumber>5</GroupNumber> <GroupNumber>6</GroupNumber>
<FileNumber>31</FileNumber> <FileNumber>31</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/tasks.c</PathWithFileName> <PathWithFileName>..\Drivers\LwIP\src\core\def.c</PathWithFileName>
<FilenameWithoutPath>tasks.c</FilenameWithoutPath> <FilenameWithoutPath>def.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
<File> <File>
<GroupNumber>5</GroupNumber> <GroupNumber>6</GroupNumber>
<FileNumber>32</FileNumber> <FileNumber>32</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/timers.c</PathWithFileName> <PathWithFileName>..\Drivers\LwIP\src\core\inet_chksum.c</PathWithFileName>
<FilenameWithoutPath>timers.c</FilenameWithoutPath> <FilenameWithoutPath>inet_chksum.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
<File> <File>
<GroupNumber>5</GroupNumber> <GroupNumber>6</GroupNumber>
<FileNumber>33</FileNumber> <FileNumber>33</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c</PathWithFileName> <PathWithFileName>..\Drivers\LwIP\src\core\init.c</PathWithFileName>
<FilenameWithoutPath>cmsis_os2.c</FilenameWithoutPath> <FilenameWithoutPath>init.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
<File> <File>
<GroupNumber>5</GroupNumber> <GroupNumber>6</GroupNumber>
<FileNumber>34</FileNumber> <FileNumber>34</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c</PathWithFileName> <PathWithFileName>..\Drivers\LwIP\src\core\ip.c</PathWithFileName>
<FilenameWithoutPath>heap_4.c</FilenameWithoutPath> <FilenameWithoutPath>ip.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
<File> <File>
<GroupNumber>5</GroupNumber> <GroupNumber>6</GroupNumber>
<FileNumber>35</FileNumber> <FileNumber>35</FileNumber>
<FileType>1</FileType> <FileType>1</FileType>
<tvExp>0</tvExp> <tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2> <bDave2>0</bDave2>
<PathWithFileName>../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3/port.c</PathWithFileName> <PathWithFileName>..\Drivers\LwIP\src\core\mem.c</PathWithFileName>
<FilenameWithoutPath>port.c</FilenameWithoutPath> <FilenameWithoutPath>mem.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>36</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\memp.c</PathWithFileName>
<FilenameWithoutPath>memp.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>37</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\netif.c</PathWithFileName>
<FilenameWithoutPath>netif.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>38</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\pbuf.c</PathWithFileName>
<FilenameWithoutPath>pbuf.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>39</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\raw.c</PathWithFileName>
<FilenameWithoutPath>raw.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>40</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\stats.c</PathWithFileName>
<FilenameWithoutPath>stats.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>41</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\sys.c</PathWithFileName>
<FilenameWithoutPath>sys.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>42</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\tcp.c</PathWithFileName>
<FilenameWithoutPath>tcp.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>43</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\tcp_in.c</PathWithFileName>
<FilenameWithoutPath>tcp_in.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>44</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\tcp_out.c</PathWithFileName>
<FilenameWithoutPath>tcp_out.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>45</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\timeouts.c</PathWithFileName>
<FilenameWithoutPath>timeouts.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>46</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\udp.c</PathWithFileName>
<FilenameWithoutPath>udp.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>47</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\ipv4\etharp.c</PathWithFileName>
<FilenameWithoutPath>etharp.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>48</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\ipv4\icmp.c</PathWithFileName>
<FilenameWithoutPath>icmp.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>49</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\ipv4\ip4.c</PathWithFileName>
<FilenameWithoutPath>ip4.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>50</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\ipv4\ip4_addr.c</PathWithFileName>
<FilenameWithoutPath>ip4_addr.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>6</GroupNumber>
<FileNumber>51</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\core\ipv4\ip4_frag.c</PathWithFileName>
<FilenameWithoutPath>ip4_frag.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
</Group>
<Group>
<GroupName>Drivers/LwIP/netif</GroupName>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>
<File>
<GroupNumber>7</GroupNumber>
<FileNumber>52</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\netif\ethernet.c</PathWithFileName>
<FilenameWithoutPath>ethernet.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>7</GroupNumber>
<FileNumber>53</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Drivers\LwIP\src\netif\ethernetif.c</PathWithFileName>
<FilenameWithoutPath>ethernetif.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
</Group>
<Group>
<GroupName>APP</GroupName>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>
<File>
<GroupNumber>8</GroupNumber>
<FileNumber>54</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\App\config.c</PathWithFileName>
<FilenameWithoutPath>config.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>8</GroupNumber>
<FileNumber>55</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\App\flash_param.c</PathWithFileName>
<FilenameWithoutPath>flash_param.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>8</GroupNumber>
<FileNumber>56</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\App\tcp_client.c</PathWithFileName>
<FilenameWithoutPath>tcp_client.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>8</GroupNumber>
<FileNumber>57</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\App\tcp_server.c</PathWithFileName>
<FilenameWithoutPath>tcp_server.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>8</GroupNumber>
<FileNumber>58</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\App\uart_trans.c</PathWithFileName>
<FilenameWithoutPath>uart_trans.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
</Group>
<Group>
<GroupName>Middlewares/SEGGER_RTT</GroupName>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>
<File>
<GroupNumber>9</GroupNumber>
<FileNumber>59</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Middlewares\Third_Party\SEGGER_RTT\SEGGER_RTT.c</PathWithFileName>
<FilenameWithoutPath>SEGGER_RTT.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>9</GroupNumber>
<FileNumber>60</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>..\Middlewares\Third_Party\SEGGER_RTT\SEGGER_RTT_printf.c</PathWithFileName>
<FilenameWithoutPath>SEGGER_RTT_printf.c</FilenameWithoutPath>
<RteFlg>0</RteFlg> <RteFlg>0</RteFlg>
<bShared>0</bShared> <bShared>0</bShared>
</File> </File>
+7 -2
View File
@@ -55,7 +55,7 @@
<CreateHexFile>1</CreateHexFile> <CreateHexFile>1</CreateHexFile>
<DebugInformation>1</DebugInformation> <DebugInformation>1</DebugInformation>
<BrowseInformation>1</BrowseInformation> <BrowseInformation>1</BrowseInformation>
<ListingPath>TCP2UART\</ListingPath> <ListingPath>.\TCP2UART\</ListingPath>
<HexFormatSelection>1</HexFormatSelection> <HexFormatSelection>1</HexFormatSelection>
<Merge32K>0</Merge32K> <Merge32K>0</Merge32K>
<CreateBatchFile>0</CreateBatchFile> <CreateBatchFile>0</CreateBatchFile>
@@ -337,7 +337,7 @@
<v6WtE>0</v6WtE> <v6WtE>0</v6WtE>
<v6Rtti>0</v6Rtti> <v6Rtti>0</v6Rtti>
<VariousControls> <VariousControls>
<MiscControls></MiscControls> <MiscControls>--diag_suppress=111,128</MiscControls>
<Define>USE_HAL_DRIVER,STM32F103xB</Define> <Define>USE_HAL_DRIVER,STM32F103xB</Define>
<Undefine></Undefine> <Undefine></Undefine>
<IncludePath>../Core/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;../Drivers/CH390;../Drivers/LwIP/src/include;../Drivers/LwIP/src/include/lwip;../Drivers/LwIP/src/include/netif;../Drivers/LwIP/src/include/arch;../Drivers/LwIP/src/netif;../App;../Middlewares/Third_Party/SEGGER_RTT</IncludePath> <IncludePath>../Core/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;../Drivers/CH390;../Drivers/LwIP/src/include;../Drivers/LwIP/src/include/lwip;../Drivers/LwIP/src/include/netif;../Drivers/LwIP/src/include/arch;../Drivers/LwIP/src/netif;../App;../Middlewares/Third_Party/SEGGER_RTT</IncludePath>
@@ -549,6 +549,11 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\Drivers\CH390\CH390_Interface.c</FilePath> <FilePath>..\Drivers\CH390\CH390_Interface.c</FilePath>
</File> </File>
<File>
<FileName>ch390_runtime.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\CH390\ch390_runtime.c</FilePath>
</File>
</Files> </Files>
</Group> </Group>
<Group> <Group>
Binary file not shown.
+1
View File
@@ -32,6 +32,7 @@
Stack_Size EQU 0x400 Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3 AREA STACK, NOINIT, READWRITE, ALIGN=3
EXPORT Stack_Mem
Stack_Mem SPACE Stack_Size Stack_Mem SPACE Stack_Size
__initial_sp __initial_sp
+1 -1
View File
@@ -16,7 +16,7 @@
#endif #endif
#ifndef BUFFER_SIZE_UP #ifndef BUFFER_SIZE_UP
#define BUFFER_SIZE_UP 1024 #define BUFFER_SIZE_UP 256
#endif #endif
#ifndef BUFFER_SIZE_DOWN #ifndef BUFFER_SIZE_DOWN
+5 -5
View File
@@ -102,14 +102,14 @@ SECTIONS
. = ALIGN(4); . = ALIGN(4);
} >FLASH } >FLASH
.ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ .ARM.extab :
{ {
. = ALIGN(4); . = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*) *(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4); . = ALIGN(4);
} >FLASH } >FLASH
.ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ .ARM :
{ {
. = ALIGN(4); . = ALIGN(4);
__exidx_start = .; __exidx_start = .;
@@ -118,7 +118,7 @@ SECTIONS
. = ALIGN(4); . = ALIGN(4);
} >FLASH } >FLASH
.preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ .preinit_array :
{ {
. = ALIGN(4); . = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .); PROVIDE_HIDDEN (__preinit_array_start = .);
@@ -127,7 +127,7 @@ SECTIONS
. = ALIGN(4); . = ALIGN(4);
} >FLASH } >FLASH
.init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ .init_array :
{ {
. = ALIGN(4); . = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .); PROVIDE_HIDDEN (__init_array_start = .);
@@ -137,7 +137,7 @@ SECTIONS
. = ALIGN(4); . = ALIGN(4);
} >FLASH } >FLASH
.fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ .fini_array :
{ {
. = ALIGN(4); . = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .); PROVIDE_HIDDEN (__fini_array_start = .);
+4 -4
View File
@@ -234,10 +234,10 @@ RCC.USBFreq_Value=72000000
RCC.VCOOutput2Freq_Value=8000000 RCC.VCOOutput2Freq_Value=8000000
SH.GPXTI0.0=GPIO_EXTI0 SH.GPXTI0.0=GPIO_EXTI0
SH.GPXTI0.ConfNb=1 SH.GPXTI0.ConfNb=1
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8 SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_2
SPI1.CLKPhase=SPI_PHASE_2EDGE SPI1.CLKPhase=SPI_PHASE_1EDGE
SPI1.CLKPolarity=SPI_POLARITY_HIGH SPI1.CLKPolarity=SPI_POLARITY_LOW
SPI1.CalculateBaudRate=9.0 MBits/s SPI1.CalculateBaudRate=36.0 MBits/s
SPI1.Direction=SPI_DIRECTION_2LINES SPI1.Direction=SPI_DIRECTION_2LINES
SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler,VirtualNSS,CLKPolarity,CLKPhase SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler,VirtualNSS,CLKPolarity,CLKPhase
SPI1.Mode=SPI_MODE_MASTER SPI1.Mode=SPI_MODE_MASTER
+37
View File
@@ -11,10 +11,16 @@ set(MX_Defines_Syms
# STM32CubeMX generated include paths # STM32CubeMX generated include paths
set(MX_Include_Dirs set(MX_Include_Dirs
${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Inc
${CMAKE_CURRENT_SOURCE_DIR}/../../App
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CH390
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/include
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/include/arch
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/netif
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F1xx_HAL_Driver/Inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F1xx_HAL_Driver/Inc
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CMSIS/Device/ST/STM32F1xx/Include ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CMSIS/Device/ST/STM32F1xx/Include
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CMSIS/Include ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CMSIS/Include
${CMAKE_CURRENT_SOURCE_DIR}/../../Middlewares/Third_Party/SEGGER_RTT
) )
# STM32CubeMX generated application sources # STM32CubeMX generated application sources
@@ -30,6 +36,37 @@ set(MX_Application_Src
${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/stm32f1xx_hal_msp.c ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/stm32f1xx_hal_msp.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/sysmem.c ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/sysmem.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/syscalls.c ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/syscalls.c
${CMAKE_CURRENT_SOURCE_DIR}/../../App/config.c
${CMAKE_CURRENT_SOURCE_DIR}/../../App/flash_param.c
${CMAKE_CURRENT_SOURCE_DIR}/../../App/tcp_client.c
${CMAKE_CURRENT_SOURCE_DIR}/../../App/tcp_server.c
${CMAKE_CURRENT_SOURCE_DIR}/../../App/uart_trans.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CH390/CH390.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CH390/CH390_Interface.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CH390/ch390_runtime.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/def.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/inet_chksum.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/init.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/ip.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/mem.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/memp.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/netif.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/pbuf.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/raw.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/stats.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/sys.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/tcp.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/tcp_in.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/tcp_out.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/timeouts.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/ipv4/etharp.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/ipv4/icmp.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/ipv4/ip4.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/core/ipv4/ip4_addr.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/netif/ethernet.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/LwIP/src/netif/ethernetif.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Middlewares/Third_Party/SEGGER_RTT/SEGGER_RTT.c
${CMAKE_CURRENT_SOURCE_DIR}/../../Middlewares/Third_Party/SEGGER_RTT/SEGGER_RTT_printf.c
${CMAKE_CURRENT_SOURCE_DIR}/../../startup_stm32f103xb.s ${CMAKE_CURRENT_SOURCE_DIR}/../../startup_stm32f103xb.s
) )
+32
View File
@@ -0,0 +1,32 @@
param(
[string]$BindHost = "0.0.0.0",
[int]$Port = 8081,
[switch]$Echo,
[switch]$NoStdin
)
$listeners = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
if ($listeners) {
$pids = $listeners | Select-Object -ExpandProperty OwningProcess -Unique
Write-Host "Stopping existing listeners on TCP ${Port}: $($pids -join ', ')"
foreach ($procId in $pids) {
try {
Stop-Process -Id $procId -Force -ErrorAction Stop
}
catch {
Write-Warning "Failed to stop process $procId : $_"
}
}
Start-Sleep -Milliseconds 300
}
$args = @("tools/tcp_debug_server.py", "--host", $BindHost, "--port", "$Port")
if ($Echo) {
$args += "--echo"
}
if ($NoStdin) {
$args += "--no-stdin"
}
Write-Host "Starting TCP debug server on ${BindHost}:${Port}"
python @args
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Simple TCP debug server for validating raw payload forwarding.
Usage examples:
python tools/tcp_debug_server.py --host 0.0.0.0 --port 8081
python tools/tcp_debug_server.py --port 8081 --echo
Features:
- Listen as a plain TCP server
- Print connect/disconnect events
- Print received payload as text and hex
- Optional echo mode
- Optional stdin -> socket sender for manual testing
"""
from __future__ import annotations
import argparse
import select
import socket
import sys
import threading
from datetime import datetime
def ts() -> str:
return datetime.now().strftime("%H:%M:%S.%f")[:-3]
def hex_bytes(data: bytes) -> str:
return " ".join(f"{b:02X}" for b in data)
def text_view(data: bytes) -> str:
return "".join(chr(b) if 32 <= b < 127 else "." for b in data)
def sender_loop(conn: socket.socket, stop_event: threading.Event) -> None:
print(f"[{ts()}] stdin sender ready. Type text and press Enter to send.")
while not stop_event.is_set():
line = sys.stdin.readline()
if line == "":
stop_event.set()
break
payload = line.encode("utf-8", errors="replace")
try:
conn.sendall(payload)
print(
f"[{ts()}] TX {len(payload)} bytes | text={payload!r} | hex={hex_bytes(payload)}"
)
except OSError as exc:
print(f"[{ts()}] TX failed: {exc}")
stop_event.set()
break
def serve(host: str, port: int, echo: bool, no_stdin: bool) -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host, port))
server.listen(1)
print(f"[{ts()}] Listening on {host}:{port}")
conn, addr = server.accept()
with conn:
print(f"[{ts()}] Client connected from {addr[0]}:{addr[1]}")
stop_event = threading.Event()
sender_thread = None
if not no_stdin:
sender_thread = threading.Thread(
target=sender_loop, args=(conn, stop_event), daemon=True
)
sender_thread.start()
conn.setblocking(False)
while not stop_event.is_set():
ready, _, _ = select.select([conn], [], [], 0.2)
if not ready:
continue
try:
data = conn.recv(4096)
except BlockingIOError:
continue
except OSError as exc:
print(f"[{ts()}] RX failed: {exc}")
break
if not data:
print(f"[{ts()}] Client disconnected")
break
print(
f"[{ts()}] RX {len(data)} bytes | text={text_view(data)} | hex={hex_bytes(data)}"
)
if echo:
try:
conn.sendall(data)
print(f"[{ts()}] ECHO {len(data)} bytes")
except OSError as exc:
print(f"[{ts()}] Echo failed: {exc}")
break
stop_event.set()
if sender_thread is not None:
sender_thread.join(timeout=0.5)
return 0
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Minimal raw TCP debug server")
parser.add_argument(
"--host", default="0.0.0.0", help="Listen host, default: 0.0.0.0"
)
parser.add_argument(
"--port", type=int, default=8081, help="Listen port, default: 8081"
)
parser.add_argument(
"--echo", action="store_true", help="Echo received payload back to client"
)
parser.add_argument(
"--no-stdin",
action="store_true",
help="Disable stdin sender thread (receive-only mode)",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
return serve(args.host, args.port, args.echo, args.no_stdin)
except KeyboardInterrupt:
print(f"\n[{ts()}] Stopped by user")
return 0
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
+637
View File
@@ -0,0 +1,637 @@
# TCP2UART 调试指导
## 1. 适用范围
本指导面向当前 `TCP2UART` 工程,覆盖以下四类调试场景:
1. `STM32F103R8T6 + CH390D` 的基础 bring-up
2. `SEGGER RTT`、异常陷阱与主循环运行状态确认
3. `USART1` 配置口、`USART2/USART3` 数据口与 `MUX / NET / LINK[idx]` 协议联调
4. `TCP Server / TCP Client / UART` 三层数据通路联调与问题隔离
本指导默认基线如下:
1. 当前工程采用裸机主循环架构,未使用 FreeRTOS 参与主业务调度
2. `CH390` 运行时访问统一由 `ch390_runtime` 持有
3. 调试输出统一使用 `SEGGER RTT`
4. 当前应用层协议模型已经收敛到 `MUX / NET / LINK[idx]`
5. 当前代码应以 `MDK-ARM` 工程构建结果为准,而不是 `CMake + MSVC` 结果
---
## 2. 当前工程边界与真实状态
在进入现场调试前,先统一以下工程边界,避免沿用过时结论:
1. 当前项目的主要软件路径已经切换为:
- `NET`:网络基础参数
- `LINK[idx]`:链路配置记录
- `MUX`:数据口承载模式
2. 对外 AT 配置面应只围绕以下命令展开:
- `AT`
- `AT+?`
- `AT+QUERY`
- `AT+MUX`
- `AT+NET`
- `AT+LINK`
- `AT+SAVE`
- `AT+RESET`
- `AT+DEFAULT`
3. 当前 `CH390D` 的历史“全 `0xFF` / 全 `0x0000`”结论不应再直接沿用。
4. 已有结论表明:
- MCU 启动、RTT、主循环、TIM4 心跳路径可工作
- `CH390D` 基础寄存器读写与 `lwIP netif` 基本链路已经打通过一次
- 真实硬件侧曾定位到 `CH390D` 供电滤波电容虚焊问题
5. 因此,当前调试重点不再是“CH390 是否完全不通”,而是:
- 启动阶段是否稳定
- `MUX / NET / LINK[idx]` 协议是否与代码一致
- UART / TCP / CH390 三层通路是否协同稳定
- 参数保存、复位和恢复流程是否可靠
---
## 3. 代码入口与调试责任边界
### 3.1 启动与主循环入口
以下代码路径是 bring-up 的第一现场:
1. `Core/Src/main.c`
- `main()`:总启动入口
- `SystemClock_Config()`:时钟初始化
- `App_Init()`:应用层初始化
- `App_Poll()`:主循环核心路径
- `BootDiag_ReportCh390()`:启动阶段 CH390 诊断输出
2. `Core/Src/stm32f1xx_it.c`
- 故障与中断入口
- `USART1/2/3``EXTI0`、DMA 回调等联调关键入口
### 3.2 CH390 责任边界
当前 CH390 调试必须遵守以下责任边界:
1. `Drivers/CH390/CH390_Interface.c`
- 只负责 GPIO / SPI / 寄存器与内存事务
2. `Drivers/CH390/CH390.c`
- 只负责芯片级 helper,例如默认配置、PHY、MAC 读写
3. `Drivers/CH390/ch390_runtime.c`
- 唯一的运行时拥有者
- 负责初始化、链路检查、IRQ 消费、RX/TX 服务与诊断快照
4. `Drivers/LwIP/src/netif/ethernetif.c`
- 只承担 netif glue 与轮询桥接,不应重新下沉复杂 CH390 运行时事务
5. `Core/Src/main.c`
- 启动后只通过 runtime 对外暴露的诊断与轮询接口工作
调试时不要把原始 CH390 寄存器访问重新散回 `main.c`、中断或多个业务层。
### 3.3 配置口与业务口边界
1. `USART1`
- 配置口
- 负责接收 `AT` 命令
- 当前接收逻辑在:
- `Core/Src/stm32f1xx_it.c``HAL_UART_RxCpltCallback()`
- `App/config.c``config_uart_rx_byte()` / `config_poll()` / `config_process_at_cmd()`
2. `USART2 / USART3`
- 数据口
- 负责普通透传或 MUX 承载
- 当前入口在 `App/uart_trans.c`
---
## 4. 当前硬件与调试工具基线
### 4.1 核心硬件对象
1. MCU`STM32F103R8T6`
2. 以太网芯片:`CH390D`
3. 配置串口:`USART1`
4. 数据串口:`USART2 / USART3`
5. 调试输出:`SEGGER RTT`
### 4.2 构建与下载基线
当前建议优先使用以下工程与产物:
1. `MDK-ARM/TCP2UART.uvprojx`
2. `MDK-ARM/TCP2UART/TCP2UART.axf`
3. `MDK-ARM/TCP2UART/TCP2UART.hex`
4. `MDK-ARM/TCP2UART/TCP2UART.map`
5. `MDK-ARM/TCP2UART/TCP2UART.build_log.htm`
6. `build_keil.log`
说明:
1. 当前 `CMake configure` 可以完成,但 `CMake + MSVC` 不适合作为 STM32/CMSIS 的最终构建验收依据。
2. 若需要验证“当前代码是否真实可编译”,优先看 `MDK-ARM` 构建产物与日志。
### 4.3 常用调试工具
1. `Keil MDK-ARM`
2. `ST-Link / J-Link`
3. `SEGGER RTT Viewer`
4. `PowerShell`
5. `tools/start_tcp_debug_server.ps1`
6. `tools/tcp_debug_server.py`
---
## 5. 启动阶段调试顺序
建议按 P0 ~ P5 顺序推进,不要跳层。
### 5.1 P0:确认最小基础条件
每次现场调试前,先确认:
1. `MDK-ARM` 可构建并产出新的 `axf/hex/map`
2. 板卡可正常下载与复位
3. RTT 可连接并看到启动输出
4. LED 心跳可工作
5. `App_Poll()` 已经进入稳定轮询
这一层若失败,不要进入网络或协议调试。
### 5.2 P1:确认启动日志与 trap 状态
上电或复位后,优先看 RTT 输出中是否出现:
1. `TCP2UART boot`
2. 若 HSE 启动失败,则会出现:
- `WARN: HSE start failed, fallback to HSI PLL`
3. `BootDiag_ReportCh390()` 输出的 CH390 诊断与网络配置快照
若发生异常,优先观察是否打印:
1. `TRAP: Error_Handler`
2. `TRAP: HardFault_Handler`
3. `TRAP: MemManage_Handler`
4. `TRAP: BusFault_Handler`
5. `TRAP: UsageFault_Handler`
当前 trap 统一收敛到:
1. `Core/Src/main.c``Debug_TrapWithRttHint()`
2. 它会打印 RTT、执行 `__BKPT(0)` 并停住
因此,若 RTT 中出现 `TRAP:`,应立即接调试器看断点现场,而不是继续盲猜高层逻辑。
### 5.3 P2:确认 CH390 初始化链路
启动阶段应重点关注 `Drivers/CH390/ch390_runtime.c` 中初始化阶段日志,理想情况下应能依次看到:
1. `ETH init: gpio`
2. `ETH init: spi`
3. `ETH init: reset`
4. `ETH init: probe`
5. `ETH init: default`
6. `ETH init: mac`
7. `ETH init: getmac`
8. `ETH init: irq`
9. `ETH init: done`
此阶段重点判定:
1. `VID / PID / REV` 是否可信
2. PHY 寄存器是否稳定可读
3. MAC 写入与回读是否一致
4. `link_up` 是否与真实网线状态一致
若这一层失败,优先做硬件侧量测,而不是先改业务层:
1. `RSTB`
2. `CS`
3. `SCK`
4. `MOSI`
5. `MISO`
6. `INT`
7. `VDDK`
8. `AVDD33 / VDDIO / AVDD33`
9. `XI / XO`
---
## 6. USART1 配置口调试
### 6.1 当前命令面
根据当前代码与手册,配置口应围绕以下命令验证:
1. `AT`
2. `AT+?`
3. `AT+QUERY`
4. `AT+MUX=...`
5. `AT+MUX?`
6. `AT+NET=...`
7. `AT+NET?`
8. `AT+BAUD=...`
9. `AT+BAUD?`
10. `AT+LINK=...`
11. `AT+LINK?`
12. `AT+SAVE`
13. `AT+RESET`
14. `AT+DEFAULT`
其中与数据串口相关的固定映射为:
1. `U0 = USART2`
2. `U1 = USART3`
3. `AT+BAUD=U0,<baud>` / `AT+BAUD=U1,<baud>` 只更新当前运行配置记录
4. 新波特率不会立即重初始化 `USART2/USART3`,必须执行 `AT+SAVE` + `AT+RESET` 后按保存值生效
5. 当前代码接受的波特率范围为 `1200 ~ 921600`
### 6.2 现场关键规则
根据已有联调记录,配置口最关键的 bench 规则是:
1. 对外手册统一要求 AT 文本以 `\r\n` 结束,现场工具也应优先按这个格式发送。
2. 若主机侧发送方式不对,现象会很像“配置口完全无响应”。
3. 因此,配置口不响应时,第一优先级不是改 parser,而是先验证主机端发送格式与接线。
4. 当前实现可能容忍只以 `\n` 结束的输入,但这只是接收实现细节,不作为对外协议口径。
5. `BAUD` 类命令若查询值已变化,但 `USART2/USART3` 现场波特率尚未变化,不应立即归因为命令无效,应先确认是否已经执行 `AT+SAVE``AT+RESET`
### 6.3 最小验证步骤
建议按以下顺序验证:
1. 连接 `USART1`
2. 先发 `AT`
3. 再发 `AT+QUERY`
4. 再发 `AT+NET?`
5. 再发 `AT+BAUD?`
6. 再发 `AT+LINK?`
7. 修改一个最小参数,例如:
- `AT+MUX=1`
-`AT+BAUD=U1,38400`
8. 执行:
- `AT+SAVE`
- `AT+RESET`
9. 复位后再次查询,确认配置是否保留
10. 若本轮验证的是 `AT+BAUD`,还应同步用上位机重新按新波特率连接 `USART2/USART3`,确认数据口实际生效
### 6.4 持久化失败时怎么查
优先检查以下路径:
1. `App/config.c`
- `config_save()`
- `config_load()`
- `config_set_defaults()`
2. `App/flash_param.c`
- Flash 解锁
- 页擦除
- 半字编程
- 写后校验
3. 参数页地址:
- `0x0800FC00`
不要在还没证明 `AT+SAVE` 已真正被接受之前,就直接把 `Flash 全 FFFFFFFF` 归因到 Flash 驱动错误。
---
## 7. MUX / NET / LINK[idx] 联调指导
### 7.1 协议总则
当前协议必须按以下模型理解:
1. `MUX`:全局数据承载模式开关
2. `NET`IP / Mask / GW / MAC
3. `LINK[idx]`:链路配置项
固定链路索引映射为:
1. `LINK[0] = S1`
2. `LINK[1] = S2`
3. `LINK[2] = C1`
4. `LINK[3] = C2`
固定端点编码为:
1. `C1 = 0x01`
2. `C2 = 0x02`
3. `UART2 = 0x04`
4. `UART3 = 0x08`
5. `S1 = 0x10`
6. `S2 = 0x20`
### 7.2 MUX 数据口规则
`MUX=1` 时,数据口应使用 MUX 帧。
重点规则:
1. `DSTMASK=0x00` 表示系统控制帧
2. 控制帧中的 AT 文本必须严格按手册要求结束
3. 普通数据帧走业务转发路径,不应进入配置解析器
### 7.3 调试时重点检查什么
若怀疑 `MUX` 模式不工作,优先检查:
1. `App/uart_trans.c`
- `uart_mux_try_extract_frame()`
- `uart_mux_encode_frame()`
2. `Core/Src/main.c`
- `App_RouteMuxUartTraffic()`
- `App_RouteRawUartTraffic()`
- `App_RouteTcpTraffic()`
3. `App/config.c`
- `config_build_response_frame()`
- `config_process_at_cmd()`
推荐最小 MUX 联调顺序:
1. 先在 `MUX=0` 下跑通原始透传
2. 再切换 `MUX=1`
3. 先发一个控制帧,确认 `DSTMASK=0x00` 路径可通
4. 再发一个单目标数据帧,例如只打到 `S1`
5. 最后验证多目标位图转发
---
## 8. TCP / UART / CH390 联调顺序
### 8.1 先做链路,再做业务
`CH390` 初始化、链路和 IRQ 未被证明稳定前,不要先调高层 TCP/UART 业务。
### 8.2 推荐顺序
建议按以下顺序推进:
1. RTT 启动与 trap 状态正常
2. CH390 启动日志完整
3. 链路检测可信
4. `TCP server` / `TCP client` 建链可信
5. UART 原始透传可信
6. 再切入 `MUX` 模式联调
### 8.3 最小 TCP 调试工具
当需要验证板子是否真的把 payload 发到主机时,优先使用仓库内置最小工具:
1. `tools/tcp_debug_server.py`
- 打印连接、收包、文本视图和十六进制视图
2. `tools/start_tcp_debug_server.ps1`
- 会先清理冲突监听,再启动 Python 服务端
推荐命令:
```powershell
powershell -ExecutionPolicy Bypass -File ".\tools\start_tcp_debug_server.ps1" -Port 8081 -NoStdin
```
如需回显:
```powershell
powershell -ExecutionPolicy Bypass -File ".\tools\start_tcp_debug_server.ps1" -Port 8081 -Echo
```
直接运行 Python 服务端也可以:
```powershell
python .\tools\tcp_debug_server.py --host 0.0.0.0 --port 8081 --no-stdin
```
### 8.4 推荐验证方法
1. 先关闭 VOFA、`ncat` 和其它可能占用 `8081` 的进程
2. 启动 `start_tcp_debug_server.ps1`
3. 让板子连接主机 `TCP client` 目标端口
4. 再从主机连接板子的 `TCP server` 端口发送固定测试文本
5. 同时观察:
- Python 工具是否收到连接与 payload
- 板子 RTT 是否出现连接或错误信息
若板子 RTT 显示已连接,但主机工具无数据,优先检查本机端口占用而不是先改板端逻辑。
---
## 9. 异常、卡死与假死排查
### 9.1 看到 `TRAP:` 时怎么做
1. 先记录 RTT 中的 trap 标签
2. 立刻用调试器查看当前 PC / LR / 调用栈
3. 结合 `Core/Src/stm32f1xx_it.c` 中对应 handler 定位异常类型
### 9.2 没有 `TRAP:` 但系统不工作时怎么做
若没有 `TRAP:`,但系统表现异常,应优先区分以下情况:
1. 主循环仍在跑,只是业务路径没反应
2. 中断未到或链路未更新
3. 发生了阻塞式等待或超时问题
4. 上层工具接错端口或被错误进程抢占
### 9.3 历史上已经确认过的典型软件问题
以下问题在历史排查中已经出现过,应优先复核,不要重复踩坑:
1. PHY 访问无超时,导致永久卡死
2. 刷新未初始化的 IWDG 句柄导致 HardFault
3. 在长耗时 SPI 路径中错误扩大临界区,导致看似"系统假死"
4. 在多个层次同时触达 CH390 / SPI,导致运行时边界混乱
5. 配置口命令结束方式不对,导致误判为 parser 无响应
### 9.4 2026-04-14 MUX 模式网口失联修复记录
#### 现象
MUX 模式启动后,一段时间后网口失联。重新插拔网线无法恢复,重启后恢复正常。对端主动关闭 TCP 连接后,120 秒内无法重新建立连接。
#### 根因
对端主动关闭 TCP 连接时,`tcp_server_on_recv(p=NULL)``tcp_client_on_recv(p=NULL)` 调用 `tcp_close()` 关闭本地 pcb。`tcp_close()` 发送 FIN 后将 pcb 推入 TIME_WAIT 状态,持续 `2 × TCP_MSL = 120 秒`。在此期间 pcb 占用 `MEMP_TCP_PCB` 池(总量仅 4 个)。当多条连接同时断开后,pcb 池耗尽,新连接的 `tcp_new()` 返回 NULL。
#### 修复内容
| 文件 | 修改 | 说明 |
|------|------|------|
| `App/tcp_server.c` | `tcp_close(pcb)``tcp_abort(pcb)` | 对端关闭时立即释放 pcb,不进入 TIME_WAIT |
| `App/tcp_client.c` | `tcp_close(pcb)``tcp_abort(pcb)` | 同上 |
| `Drivers/CH390/ch390_runtime.c` | PKT_ERR 恢复时 `rcr``rcr \| RCR_RXEN` | 确保 RX 重新使能,与 WCH 官方参考一致 |
| `Drivers/CH390/ch390_runtime.c` | TX 连续超时 3 次触发 `ch390_runtime_emergency_reset()` | CH390 TX 引擎卡死时自动复位芯片 |
| `Drivers/CH390/ch390_runtime.c` | 新增 `ch390_runtime_health_check()` | 每 5 秒读 VID 验证芯片存活 |
| `Core/Src/main.c` | `App_StartLinksIfNeeded` 失败时不标记 `g_links_started` | 允许下次 poll 自动重试 |
| `Core/Src/main.c` | MUX 逐帧 RTT printf 改为 `#if DEBUG` 门控 | 生产固件不输出,减少主循环延迟 |
| `App/uart_trans.c` | `uart_mux_try_extract_frame` 先搜 0x7E 再消费 header | 非法帧只丢 1 字节而非 5 字节 |
#### 构建验证
Keil MDK-ARM 构建 0 Error(s), 0 Warning(s)。Flash 52.7 KB / 64.0 KB (82.5%)RAM 20.0 KB / 20.0 KB (100%)。
### 9.5 2026-04-18 MUX 模式丢包修复记录
#### 现象
`MUX=1` 模式下进行持续发送测试时,主机侧发送 `500` 个数据包,只收到 `360` 个,存在明显丢包。
#### 根因
本轮定位确认软件侧至少存在以下两个直接丢包点:
1. `App/uart_trans.c``uart_mux_try_extract_frame()` 在确认整帧完整前,就先消费 `SYNC` 与 header。若 MUX 帧跨越多个 poll 周期到达,半帧会被提前移出 RX ring,导致当前帧失步并被直接丢弃。
2. `App/tcp_server.c``App/tcp_client.c``Core/Src/main.c` 的发送路径对背压与短写处理不完整:
- `tcp_sndbuf() < len`
- `tcp_write()` / `tcp_output()` 返回 `ERR_MEM`
- `uart_trans_write()` 只写入部分字节
以上情况在旧代码中会被上层静默忽略,表现为“发送函数返回但数据实际未完整进入下游链路”。
#### 修复内容
| 文件 | 修改 | 说明 |
|------|------|------|
| `App/uart_trans.c` | 将 `uart_mux_try_extract_frame()` 改为先窥视、后消费 | 只有在 `SYNC + header + payload + tail` 全部可用时才推进 `rx_tail`,避免半帧被破坏性消费 |
| `App/tcp_server.c` | `tcp_server_send()``tcp_sndbuf()<len``ERR_MEM` 返回 `0` 并计入错误 | 明确表示本次发送未被底层接收,不再伪装成成功 |
| `App/tcp_client.c` | `tcp_client_send()` 同步处理背压与 `ERR_MEM` | 逻辑与 server 侧保持一致 |
| `Core/Src/main.c` | `App_SendToUart()` 检查 `uart_trans_write()` 是否完整写入 | TX ring 空间不足时立即显式失败 |
| `Core/Src/main.c` | `App_RouteTcpTraffic()` / `App_RouteRawUartTraffic()` / `App_RouteMuxUartTraffic()` 统一检查发送结果 | 不再把背压、短写和未完整提交静默当成成功 |
#### 结果验证
1. Keil MDK-ARM 构建通过,`0 Error(s), 0 Warning(s)`
2. 在最新固件下重新进行 MUX 持续发送测试,主机侧发送 `670` 个数据包,接收 `670` 个,`0` 丢包。
3. 本轮修复未增加新的常驻队列与缓冲区,保持当前 RAM 占用边界不变。
### 9.6 2026-04-24 CH390 emergency reset 恢复语义补齐记录
#### 现象
在 CH390 发生 TX timeout 并触发 `ch390_runtime_emergency_reset()` 后,芯片寄存器访问恢复正常,`VID` 可读、PHY 链路也可能保持 `up`,但 TCP 业务流量仍可能长时间不恢复,表现为“芯片还活着,但网络像失联一样,通常只能重启恢复”。
在后续实现收敛中,又确认仅依赖单次 VID 异常或单次 TX busy 即立刻 reset 过于激进,容易把瞬时抖动误判为芯片失活,因此当前代码已经演化为“带阈值的恢复策略”。
#### 根因
`ch390_runtime_emergency_reset()` 旧实现仅执行 `ch390_software_reset()``ch390_default_config()``diag` 刷新,缺少 cold init 里已有的两层恢复语义:
1. **MAC 对齐未恢复**:旧代码没有重新写回 CH390 `PAR`,也没有把硬件 MAC 重新同步到 `netif->hwaddr`。若软件复位后 CH390 的 MAC 过滤状态与 lwIP 侧缓存身份不一致,现象会表现为寄存器可访问、链路仍在,但单播业务流量不通。
2. **上层链路回收未触发**TX-timeout 路径直接调用 `ch390_runtime_emergency_reset()`,没有保证 `App_StopLinksIfNeeded()` / `App_StartLinksIfNeeded()` 观察到一次有效的 link-down 周期,导致旧 TCP client/server 状态可能跨芯片复位残留,业务层没有完成重建。
3. **恢复策略缺少抖动抑制**:若仅凭单次 TX busy 或单次 VID 异常立即 reset,容易在瞬时总线/链路抖动下过度恢复,放大业务扰动,因此当前实现增加了连续失败阈值和失败计数清零逻辑。
#### 修复内容
| 文件 | 修改 | 说明 |
|------|------|------|
| `Drivers/CH390/ch390_runtime.h` | `ch390_runtime_emergency_reset()` 改为接收 `struct netif *` | 让 reset 路径能同时修复 CH390 与 lwIP 可见状态 |
| `Drivers/CH390/ch390_runtime.c` | 抽取 `ch390_runtime_prepare_netif()` | 在 init / emergency reset 后统一恢复 `hwaddr_len``mtu``flags` 与 RX 软件状态 |
| `Drivers/CH390/ch390_runtime.c` | 新增 `ch390_runtime_sync_mac()` | emergency reset 后按当前 `netif->hwaddr` 重写 CH390 `PAR`,并重新同步硬件 MAC 到 lwIP |
| `Drivers/CH390/ch390_runtime.c` | emergency reset 成功后清 `g_ch390_irq_pending` 并置位 `g_link_restart_pending` | 避免复位前遗留中断状态影响恢复 |
| `Drivers/CH390/ch390_runtime.c` | `ch390_runtime_check_link()` 增加一次性 hold-down 逻辑 | 保证主循环至少看到一次 link-down,从而触发 app 层 stop/start 回收重建 |
| `Drivers/CH390/ch390_runtime.c` | TX-timeout 与 health-check 两条 reset 路径统一传入 `netif` | 让两类恢复路径都走同一套 MAC 重同步与链路重建语义 |
| `Drivers/CH390/ch390_runtime.c` | 为 TX timeout 与 health-check 增加连续失败阈值 | 降低瞬时抖动导致的过度 reset 风险 |
#### 当前实现语义(以源码为准)
1. **TX timeout 阈值**
- 单次判定条件:`CH390_TCR.TXREQ` busy-wait 持续超过 `10 ms`
- 连续阈值:累计 `6` 次后才触发 `ch390_runtime_emergency_reset()`
- 只要有一次成功发送,`g_tx_consecutive_timeout` 立即清零,因此该阈值针对的是**连续失败**,不是累计历史失败次数。
2. **health-check 阈值**
- `VID` 单次读到 `0x0000` / `0xFFFF` 并不会立即 reset。
- 只有连续 `3` 次异常 VID 才触发 emergency reset。
-`g_ch390_ready == 0`,则 health-check 会直接尝试 reset,不再等待 VID 连续计数。
3. **restart-pending 的单次语义**
- emergency reset 成功后会置位 restart-pending。
- 下一次 `ch390_runtime_check_link()` 先强制执行一次 `netif_set_link_down()`,随后立即清除此标志并提前返回。
- 该设计用于保证主循环至少看到一次有效的 logical link-down,从而沿用现有 `App_StopLinksIfNeeded()` / `App_StartLinksIfNeeded()` 路径回收并重建 TCP links。
4. **内部计数与状态**
- `g_chip_reset_count`:记录 emergency reset 尝试次数,饱和递增到 `0xFF`
- `g_tx_consecutive_timeout`:记录连续 TX busy 超时次数;成功发送或进入 reset 路径后清零。
- health-check 连续失败计数当前与 `g_link_restart_pending` 共用一个状态字节的高位 nibble 存储;当 VID 恢复正常、达到 reset 阈值或 emergency reset 成功时会清零。
5. **失败路径差异**
- 只有当 emergency reset 完成后 `g_diag.id_valid` 仍然有效,才会置位 restart-pending 并进入后续 app recycle 语义。
- 若 reset 后芯片仍不响应,则仅记录失败并返回,不会伪装成可恢复状态。
#### 预期结果
1. CH390 发生 emergency reset 后,硬件 MAC、`netif->hwaddr` 与当前业务身份重新对齐。
2. 即使物理网线始终保持连接,主循环仍会在后续 poll 中观察到一次有效 link-down,并按既有 `App_StopLinksIfNeeded()` / `App_StartLinksIfNeeded()` 路径回收并重建 TCP links。
3. 恢复策略对瞬时异常更保守:只有连续超时或连续 VID 异常达到阈值才会触发 reset,降低误触发恢复的概率。
4. 复位后的恢复语义与 cold init 更接近,不再停留在“芯片寄存器恢复正常,但业务流量仍死掉”的半恢复状态。
#### 构建验证
1. 已由现场手动执行工程构建,构建通过。
2. 本轮修改覆盖 `Drivers/CH390/ch390_runtime.c``Drivers/CH390/ch390_runtime.h` 与本手册记录,未改动 TCP client/server 模块对外接口。
---
## 10. 常见误区
调试当前工程时,应避免以下误区:
1. 不要继续沿用“CH390 恒为全 `0xFF`”这一过时结论
2. 不要在 `main.c`、IRQ、netif 多处重新插入原始 CH390 访问
3. 不要在没有芯片脚侧证据前,只凭 MCU 侧 GPIO 判断总线正常
4. 不要在基础寄存器读写尚不可信时,直接调高层 `TCP/UART/MUX` 业务逻辑
5. 不要把一次性 bring-up 实验代码长期留在正式路径中
6. 不要让多个本机进程同时监听板子要连接的 TCP 端口
7. 不要在尚未证明命令已真正进入 parser 之前,直接归因到 Flash、协议或网络层
---
## 11. 推荐的现场记录模板
建议每次现场调试至少记录以下信息:
1. 日期时间
2. 板卡编号
3. 固件产物路径
4. 下载方式
5. RTT 关键日志
6. 串口发送内容
7. TCP 调试工具输出
8. 关键波形或电压量测点
9. 结论
10. 下一步动作
建议记录格式:
```text
时间:
板卡:
固件:
下载方式:
操作步骤:
RTT输出:
串口/TCP现象:
硬件量测:
结论:
下一步:
```
---
## 12. 当前推荐的结论表达方式
若需要向项目成员同步当前状态,建议采用以下口径:
1. 当前工程软件架构已稳定在 `bare-metal + lwIP RAW + ch390_runtime 单一拥有者`
2. 当前调试重点已经从“CH390 是否完全无响应”转移到协议、链路和系统级联调
3. 当前对外协议和配置模型应以 `MUX / NET / LINK[idx]` 为准
4. `USART1` 配置口、`USART2/3` 数据口与 TCP 路由必须按最新代码路径调试,不应再参照历史 `IP/MASK/GW/PORT/RIP/RPORT` 公开接口模型
5. 硬件验证仍必须以 CH390 芯片脚侧波形和供电域量测为准
---
## 13. 建议配套阅读
建议与本指导配套阅读:
1. `AT固件使用手册.md`
2. `项目技术实现.md`
3. `项目需求说明.md`
4. `代码结构与阅读指南.md`
5. `项目文档索引.md`
6. `CH390_最终结论报告.md`
7. `PCB/SCH_Schematic1_2026-03-26.pdf`
8. `tools/tcp_debug_server.py`
9. `tools/start_tcp_debug_server.ps1`
+262 -203
View File
@@ -1,284 +1,343 @@
# TCP2UART 项目技术实现 # TCP2UART 项目技术实现
## 一、当前实现结论 ## 一、文档目的
当前工程已经从原先的 `FreeRTOS + lwIP socket/netconn` 方向,重构为适配 `STM32F103R8T6` 的裸机实现 本文档描述 `TCP2UART` 项目的最终内部实现口径
当前基线特征如下 本文档只围绕最终协议模型展开
1. MCU 目标固定为 `STM32F103R8T6 / STM32F103xB` - `MUX`:串口承载层
2. 软件架构改为 `bare-metal main loop + DMA/IDLE + EXTI` - `NET`:全局网络配置层
3. 网络栈采用 `lwIP RAW API + NO_SYS=1` - `LINK[idx]`:实例配置与连接管理层
4. 调试输出采用 `SEGGER RTT`
5. 构建目标已通过 `MDK-ARM` 编译,适配 `64KB Flash / 20KB SRAM`
## 二、硬件与资源约束 不再保留历史 `S1... / C1...` 外部字段模型。
### 2.1 MCU ## 二、当前工程基础
- 型号:`STM32F103R8T6` 当前工程基础约束如下:
- Flash`64 KB`
- SRAM`20 KB`
- 主频:`72 MHz`
### 2.2 主要外设 1. MCU`STM32F103R8T6`
2. 网络芯片:`CH390D`
3. 软件架构:`bare-metal main loop`
4. 协议栈:`lwIP RAW API + NO_SYS=1`
5. 调试输出:`SEGGER RTT`
6. 不使用 `FreeRTOS`
7. 不实现 DHCP
- `SPI1`:连接 `CH390D` ## 三、总体架构
- `USART1`:配置串口
- `USART2`Server 透传串口
- `USART3`Client 透传串口
- `DMA1`UART 收发 DMA
- `EXTI0`CH390 中断输入
- `IWDG`:独立看门狗
### 2.3 当前引脚分配
| 引脚 | 功能 | 用途 |
|------|------|------|
| PA2 | USART2_TX | Server 透传串口 |
| PA3 | USART2_RX | Server 透传串口 |
| PA4 | GPIO_Output | CH390D 片选 |
| PA5 | SPI1_SCK | CH390D SPI 时钟 |
| PA6 | SPI1_MISO | CH390D SPI 数据输入 |
| PA7 | SPI1_MOSI | CH390D SPI 数据输出 |
| PA9 | USART1_TX | 配置串口 |
| PA10 | USART1_RX | 配置串口 |
| PB0 | EXTI0 | CH390D INT |
| PB1 | GPIO_Output | CH390D RESET |
| PB10 | USART3_TX | Client 透传串口 |
| PB11 | USART3_RX | Client 透传串口 |
| PC13 | GPIO_Output | 状态 LED |
| PD0/PD1 | HSE | 8MHz 外部晶振 |
## 三、架构选择原因
`STM32F103R8T6` 的资源上限不足以稳定承载原方案中的以下组合:
1. `FreeRTOS`
2. `CMSIS-RTOS V2`
3. 多任务栈
4. `lwIP socket/netconn/tcpip` OS 路线
5. `StreamBuffer / Semaphore / Mutex`
因此当前实现采用如下组合:
1. 去掉 `FreeRTOS`
2. 去掉 `CMSIS-RTOS V2`
3. 去掉 `lwIP socket/netconn`
4. 改为 `lwIP RAW API + NO_SYS=1`
5. 串口与网口统一由主循环推进
## 四、当前软件架构
### 4.1 分层
```text ```text
+--------------------------------------------------+ +--------------------------------------------------+
| Application Logic | | AT / Control Plane |
| config / tcp_server / tcp_client / uart bridge | | USART1 AT parser + MUX control frame parser |
+--------------------------------------------------+ +--------------------------------------------------+
| Main Poll Loop | | Configuration Model |
| ethernetif_poll / sys_check_timeouts / watchdog | | MUX / NET / LINK[idx] |
+--------------------------------------------------+ +--------------------------------------------------+
| Peripheral/Event Layer | | Routing & Session Layer |
| UART DMA+IDLE / DMA IRQ / EXTI / SysTick | | TCP instance scheduling + UART dispatch |
+--------------------------------------------------+ +--------------------------------------------------+
| Drivers | | Transport Poll Loop |
| CH390 / lwIP netif / HAL | | ethernetif_poll / sys_check_timeouts / uart poll |
+--------------------------------------------------+
| Driver Layer |
| CH390 / lwIP netif / UART DMA+IDLE / HAL |
+--------------------------------------------------+ +--------------------------------------------------+
``` ```
### 4.2 执行模型 ## 四、最终协议实现模型
当前执行模型为: ### 4.1 MUX 帧承载层
1. `SysTick` 提供全局毫秒时基 数据口启用 MUX 后,统一处理如下帧:
2. `EXTI0` 只置位 CH390 待处理标志
3. `DMA IRQ``UART IRQ` 只完成回调分发与 IDLE 采样
4. 主循环统一执行网络轮询、超时推进、串口桥接和看门狗喂狗
## 五、当前模块实现状态 ```text
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
```
### 5.1 配置模块 实现职责:
文件:`App/config.c/.h` 1. 识别帧边界
2. 解析长度字段
3. 提取 `SRCID`
4. 解析 `DSTMASK`
5. 按控制帧或数据帧分流
已实现: ### 4.2 控制帧与数据帧分离
1. 从 Flash 加载配置 控制规则固定如下:
2. UART1 命令口接收
3. 常用网络与串口参数解析
4. 参数保存与软复位请求
当前约束: - `DSTMASK = 0x00`:系统控制帧
- `DSTMASK != 0x00`:业务数据帧
1. 构建已关闭 `DHCP`,因此 `AT+DHCP=1` 会明确返回错误 系统控制帧处理要求:
2. 配置损坏时会回退默认值,但默认值不会自动写回 Flash,仍需手动 `AT+SAVE`
### 5.2 UART 透传模块 1. `PAYLOAD` 解释为 AT 文本
2. AT 文本必须以 `\r\n` 结束
3. 控制帧进入 AT 命令处理链路
文件:`App/uart_trans.c/.h` 业务数据帧处理要求:
已实现: 1. `SRCID` 表示单一源端点
2. `DSTMASK` 表示目标端点集合
3. 路由层根据 `DSTMASK` 做多目标分发
1. `USART2/USART3` 使用 `DMA + IDLE` ### 4.3 统一端点编码
2. RX 使用 DMA 缓冲转环形缓冲
3. TX 使用 DMA 发送
4. TX/RX 完成由 `HAL_UART_*Callback` 驱动
### 5.3 TCP Server 模块 内部与外部文档统一使用以下端点编码:
文件:`App/tcp_server.c/.h` | 端点 | 编码 |
|------|------|
| `C1` | `0x01` |
| `C2` | `0x02` |
| `UART2` | `0x04` |
| `UART3` | `0x08` |
| `S1` | `0x10` |
| `S2` | `0x20` |
实现: 实现要求
1. `lwIP RAW API` 监听指定端口 - `SRCID` 为单值
2. 单连接接入 - `DSTMASK` 为位图
3. 网络数据写入本地环形缓冲 - `DSTMASK=0x00` 仅保留为控制帧
4. 主循环中与 UART2 做双向桥接
### 5.4 TCP Client 模块 ## 五、配置层设计
文件:`App/tcp_client.c/.h` ### 5.1 MUX 记录
已实现: `MUX` 为全局记录,仅控制设备数据口是否进入 MUX 承载模式。
1. `lwIP RAW API` 主动连接远端地址 取值:
2. 断链后按周期重连
3. 网络数据写入本地环形缓冲
4. 主循环中与 UART3 做双向桥接
### 5.5 CH390 与 netif 模块 - `0`:普通透传
- `1`MUX 透传
文件:`Drivers/CH390/*``Drivers/LwIP/src/netif/*` ### 5.2 NET 记录
已实现 `NET` 为全局静态网络记录
1. `SPI1 + GPIO CS + EXTI0` 驱动 CH390 ```text
2. `ethernetif.c` 采用 `NO_SYS=1` 路线 IP,MASK,GW,MAC
3. CH390 中断在主循环中轮询处理 ```
4. 配置中的 MAC 地址会在初始化时写入 CH390
### 5.6 RTT 调试输出 说明:
文件:`Middlewares/Third_Party/SEGGER_RTT/*` - 设备只有一张网卡,因此不为每个实例单独配置本地 IP
- 当前实现目标中不包含 DHCP
已实现: ### 5.3 LINK 记录
1. 工程内置最小 `SEGGER RTT` 源文件 `LINK[idx]` 为统一实例记录:
2. `main.c``printf/fputc` 已重定向到 RTT
### 5.7 TIM4 心跳闪烁 ```text
EN,LPORT,RIP,RPORT,UART
```
文件:`Core/Src/tim.c``Core/Src/main.c``Core/Src/stm32f1xx_it.c` 固定索引映射:
已实现: - `0 = S1`
- `1 = S2`
- `2 = C1`
- `3 = C2`
1. 使用 `TIM4` 作为 LED 心跳定时器 字段职责:
2. `TIM4` 时钟来自 `APB1 Timer Clock = 72 MHz`
3. 通过 `Prescaler = 7199``Period = 9` 生成 `1 ms` 更新中断
4.`HAL_TIM_PeriodElapsedCallback()` 中进行 1ms 计数
5. 累计 `1000` 次后翻转一次 `PC13`,形成 `1 s` 闪烁节拍
当前实现说明: - `EN`:实例启用状态
- `LPORT`:本地端口
- `RIP / RPORT`:对端地址与端口
- `UART`:对应业务数据口
1. `PC13` 为低电平点亮、高电平熄灭 说明:
2. 当前逻辑为每 `1 s` 翻转一次 LED 状态,即完整亮灭周期为 `2 s`
3. 若后续需要“每 1 秒完整闪烁一次”,可改为 `500 ms` 翻转一次
## 六、lwIP 配置策略 - `Server``Client` 共享同一记录结构
- `Server``RIP / RPORT` 可作为对端约束或预设
- `Client``RIP / RPORT` 表示远端目标
当前 `lwIP` 配置以适配 `R8T6` 资源为原则,核心策略如下: ## 六、模块职责调整
1. `NO_SYS = 1` ### 6.1 配置模块 `config.c/.h`
2. `LWIP_SOCKET = 0`
3. `LWIP_NETCONN = 0`
4. `LWIP_NETIF_API = 0`
5. `LWIP_DHCP = 0`
6. `LWIP_UDP = 0`
7. `LWIP_DNS = 0`
8. `LWIP_IGMP = 0`
9. 关闭 `lwIP` 平台诊断 `printf`
同时从 `MDK` 工程中移除了 最终职责
1. `FreeRTOS` 相关源码 1. 解析 `AT+MUX`
2. `lwIP api/socket/netconn/tcpip` 路线源码 2. 解析 `AT+NET`
3. `altcp / autoip / acd / dhcp / dns / igmp` 等当前不需要模块 3. 解析 `AT+LINK`
4. 加载与保存配置
5. 处理 `SAVE / RESET / DEFAULT`
## 七、主循环实际骨架 不再以历史展开式字段作为外部接口模型。
当前主循环逻辑可概括为: ### 6.2 UART 透传模块 `uart_trans.c/.h`
最终职责:
1. 保持 `USART2 / USART3``DMA + IDLE` 接收发送基线
2.`MUX=0` 时执行普通透传
3.`MUX=1` 时执行 MUX 帧收发
4. 将控制帧与业务数据帧分流
### 6.3 TCP Server / Client 模块
最终职责:
1. 不再从外部协议角度区分不同字段模型
2. 统一受 `LINK[idx]` 配置驱动
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 不监听或静默丢 SYNTCP 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_client_poll(); App_StopLinksIfNeeded();
uart_trans_poll(); App_StartLinksIfNeeded();
config_poll(); App_RouteRawUartTraffic();
App_RouteMuxUartTraffic();
tcp_server <-> UART2; App_RouteTcpTraffic();
tcp_client <-> UART3;
if (reset_requested) { if (reset_requested) {
NVIC_SystemReset(); NVIC_SystemReset();
} }
HAL_IWDG_Refresh(&hiwdg);
} }
``` ```
## 八、当前构建状态 当前实现要求:
### 8.1 MDK 构建命令 1. 统一由 `LINK[idx]` 驱动实例状态
2. 统一由 `MUX` 决定数据口承载模式
3. RAW 路由与 MUX 路由分别由 `App_RouteRawUartTraffic()``App_RouteMuxUartTraffic()``App_RouteTcpTraffic()` 执行
```bat ## 八、实现边界
"C:\Keil_v5\UV4\UV4.exe" -b "D:\code\STM32Project\TCP2UART\MDK-ARM\TCP2UART.uvprojx" -j0
```
### 8.2 当前结果 1. 保持单网卡静态网络模型
2. 不实现 DHCP
3. 不实现旧 `S1... / C1...` 外部协议字段
4. 不在文档中保留兼容层描述
5. 所有 AT 文本控制统一要求 `\r\n` 结束
当前构建结果: ## 九、文档一致性要求
1. `0 Error(s)` 后续实现、联调、测试与代码注释必须遵守以下统一口径:
2. `0 Warning(s)`
3. `Code=39988`
4. `RO-data=1272`
5. `RW-data=172`
6. `ZI-data=19188`
说明当前版本已经满足: 1. 对外协议只使用 `MUX / NET / LINK`
2. 控制帧只使用 `DSTMASK=0x00`
1. `STM32F103R8T6 64KB Flash` 约束 3. MUX 帧格式固定为 `SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL`
2. `20KB SRAM` 约束 4. AT 手册、需求说明、技术实现三份文档不得再出现历史展开式字段
3. `MDK-ARM` 可直接编译
## 九、当前已知限制与待验证项
### 9.1 功能限制
1. 当前使用静态 IP,不支持 DHCP
2. 目前未提供上板网络与串口吞吐测试结论
3. `config` 模块仍保留较重的字符串解析逻辑,但当前体积已可接受
### 9.2 上板验证重点
1. 验证 CH390 INT 极性与 EXTI 触发行为
2. 验证 `SPI1` 与 CH390 的稳定性
3. 验证 `TIM4` 1ms 中断稳定性与 `PC13` 1秒翻转节拍
4. 验证 `UART2/UART3 DMA + IDLE` 在长连续流量下的行为
5. 验证 TCP Server 与 TCP Client 双链路同时工作时的稳定性
6. 验证配置保存、复位、MAC 生效路径
## 十、后续建议
下一阶段建议按以下顺序推进:
1. 上板联调 CH390 链路与 RTT 输出
2. 验证 UART2/3 透传功能
3. 补充双向透传稳定性与丢包测试
4. 视需要继续优化 `config.c` 的体积与命令集
5. 若后续必须支持 DHCP,再单独评估资源预算
+45
View File
@@ -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 位置,避免污染项目根目录。
+130 -64
View File
@@ -1,95 +1,161 @@
# TCP2UART 项目需求说明 # TCP2UART 项目需求说明
## 一、项目概述 ## 一、项目目标
基于 `STM32F103R8T6` `CH390D` 实现双链路 TCP 串口透传设备。设备提供一条 TCP Server 链路和一条 TCP Client 链路,分别与两路 UART 做双向透明传输,并通过 UART1 进行参数配置 本项目基于 `STM32F103R8T6` `CH390D` 实现一台多实例 TCP 与双串口数据透传设备
当前项目实现路线已经固定为: 最终对外协议模型固定为:
1. `STM32CubeMX + HAL` 1. `MUX`:控制串口侧是否采用 MUX 承载
2. `bare-metal` 2. `NET`:全局静态网络配置
3. `lwIP RAW API + NO_SYS=1` 3. `LINK[idx]`:按实例索引组织的链路配置
4. `SEGGER RTT` 调试输出
不再采用 `FreeRTOS` 作为正式交付架构。 系统必须支持:
## 二、硬件平台 - `2` 路 TCP Server 实例
- `2` 路 TCP Client 实例
- `UART1` 作为 AT 配置口
- `UART2 / UART3` 作为业务数据口
| 项目 | 说明 | ## 二、硬件与软件边界
### 2.1 硬件边界
- 主控:`STM32F103R8T6`
- 以太网芯片:`CH390D`
- 网卡数量:`1`
- 配置口:`UART1`
- 数据口:`UART2``UART3`
### 2.2 软件边界
- 执行模型:`bare-metal`
- 网络协议栈:`lwIP RAW API + NO_SYS=1`
- 调试输出:`SEGGER RTT`
- 不采用 `FreeRTOS`
- 不采用 `socket/netconn`
- 不包含 DHCP 协议支持
## 三、最终协议需求
### 3.1 MUX 帧格式
所有 MUX 数据承载必须使用如下格式:
```text
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
```
要求:
- `DSTMASK != 0x00`:业务数据帧
- `DSTMASK = 0x00`:系统控制帧
- 系统控制帧承载 AT 文本命令
- AT 文本命令必须以 `\r\n` 结尾
### 3.2 统一端点编码
系统必须使用统一端点编码,同时覆盖 UART 与 TCP 逻辑实例:
| 端点 | 编码 |
|------|------| |------|------|
| 主控芯片 | `STM32F103R8T6` | | `C1` | `0x01` |
| 以太网芯片 | `CH390D` | | `C2` | `0x02` |
| PCB 设计工具 | 立创 EDA | | `UART2` | `0x04` |
| 串口通道 | `UART1 + UART2 + UART3` | | `UART3` | `0x08` |
| `S1` | `0x10` |
| `S2` | `0x20` |
要求:
- `SRCID` 为单值
- `DSTMASK` 为位图
- `DSTMASK=0x00` 仅保留给系统控制帧
## 四、AT 接口需求
### 4.1 命令分类
AT 协议必须收敛为以下三类命令:
1. `AT+MUX`
2. `AT+NET`
3. `AT+LINK`
不再保留历史展开式实例字段命令。
### 4.2 MUX 命令需求
- `AT+MUX=0/1`:设置全局 MUX 模式
- `AT+MUX?`:查询当前 MUX 模式
### 4.3 NET 命令需求
`NET` 必须统一表达以下静态网络参数:
```text
IP,MASK,GW,MAC
```
说明: 说明:
1. `UART1` 用于配置口 - 设备只有一张网卡,因此本地 IP 不按实例拆分
2. `UART2` 对应 TCP Server 透传链路 - DHCP 不属于协议需求范围
3. `UART3` 对应 TCP Client 透传链路
## 三、软件平台 ### 4.4 LINK 命令需求
| 项目 | 说明 | `LINK[idx]` 必须统一表达如下字段:
|------|------|
| 开发环境 | `STM32CubeMX + HAL + MDK-ARM` |
| 执行模型 | 裸机主循环 + 中断驱动 |
| 协议栈 | `lwIP RAW API` |
| 调试输出 | `SEGGER RTT` |
## 四、核心功能需求 ```text
EN,LPORT,RIP,RPORT,UART
```
### 4.1 双链路 TCP 通信 要求:
- `Server` 链路:设备作为 TCP Server,监听指定端口 - `idx` 固定映射四个实例:`0=S1``1=S2``2=C1``3=C2`
- `Client` 链路:设备作为 TCP Client,主动连接远程服务器 - `Server``Client` 共用同一条 `LINK` 配置模型
- 两条链路共享同一个设备 IP 地址 - `LPORT` 必须可配置
- `RIP / RPORT` 必须可配置
- `UART` 必须可配置
### 4.2 串口透传 ## 五、功能需求
- `Server` 链路数据 <=> `UART2` 双向透传 ### 5.1 TCP 功能
- `Client` 链路数据 <=> `UART3` 双向透传
- 仅透传 TCP Payload,不解析业务层协议
### 4.3 参数配置 - 支持 `2` 路 Server
- 支持 `2` 路 Client
- 每个实例通过 `LINK[idx]` 配置其本地端口、对端地址、对端端口和串口路由
- 通过 `UART1` 配置网络与串口参数 ### 5.2 串口透传功能
- 配置参数掉电保存
- 支持设备复位后按保存配置生效
### 4.4 调试与维护 - `UART2 / UART3` 支持普通透传模式与 MUX 透传模式
- 当需要多实例共享数据口时,必须启用 MUX 模式
- 业务数据流向由 `SRCID / DSTMASK` 决定
- 调试输出统一走 `SEGGER RTT` ### 5.3 系统控制功能
- 工程需可在 `MDK-ARM` 下直接构建
## 五、当前实现边界 - 系统控制帧由 `DSTMASK=0x00` 表示
- 系统控制帧进入 AT 解析路径
- 控制文本必须以 `\r\n` 结束
基于 `STM32F103R8T6``64KB Flash / 20KB SRAM` 约束,当前交付版本约束如下: ### 5.4 参数保存功能
1. 使用静态 IP - 参数修改后支持 `SAVE`
2. 当前构建不支持 DHCP - 支持 `RESET` 后按保存配置启动
3. 不使用 `lwIP socket/netconn` - 支持恢复默认配置
4. 不使用 `FreeRTOS`
这不是降级,而是基于资源约束后的正式实现路线。 ## 六、非功能需求
## 六、数据可靠性要求 1. 满足 `STM32F103R8T6``64KB Flash / 20KB SRAM` 约束
2. 工程可在 `MDK-ARM` 下构建
3. 调试输出统一使用 `SEGGER RTT`
4. 不引入 DHCP、DNS、UDP 等当前非目标协议
- 目标是保证 TCP 数据与串口数据双向透传稳定工作 ## 七、验收口径
- 需要后续补充上板联调后的丢包率测试方案与结果
- 需要验证双链路同时工作时的稳定性
## 七、交付物 验收时以下几点必须同时成立:
1. 原理图及 PCB 设计文件 1. 文档只使用 `MUX / NET / LINK` 作为最终协议模型
2. STM32 固件源码 2. 文档不再出现历史 `S1... / C1...` 外部字段
3. `CubeMX` 工程与 `MDK-ARM` 工程 3. 串口控制文本统一规定为 `\r\n` 结束
4. 使用说明文档 4. MUX 帧格式与端点编码在需求、手册、技术实现三份文档中表述一致
5. 后续补充的透传与丢包测试结果
## 八、约束条件
1. 通信协议为标准 TCP/IP
2. 串口透传为纯数据透传,不解析上层协议
3. 当前正式目标器件为 `STM32F103R8T6`
4. 所有正式实现应服从 `64KB Flash / 20KB SRAM` 约束