docs: consolidate project documentation

This commit is contained in:
2026-06-10 10:18:35 +08:00
parent 0e59416477
commit ed1ece23c3
7 changed files with 69 additions and 866 deletions
+3 -2
View File
@@ -30,7 +30,7 @@
1. `MUX`:全局数据承载模式开关 1. `MUX`:全局数据承载模式开关
2. `NET`:全局静态网络配置记录 2. `NET`:全局静态网络配置记录
3. `LINK[ROLE]`:按角色名组织的链路配置记录(`S1/S2/C1/C2` 3. `LINK`:按角色名组织的链路配置记录(`S1/S2/C1/C2`
约束如下: 约束如下:
@@ -452,4 +452,5 @@ AT+RESET\r\n
- AT 命令实现:[config.c](/D:/code/STM32Project/TCP2UART/App/config.c) - AT 命令实现:[config.c](/D:/code/STM32Project/TCP2UART/App/config.c)
- 配置结构与默认值:[config.h](/D:/code/STM32Project/TCP2UART/App/config.h) - 配置结构与默认值:[config.h](/D:/code/STM32Project/TCP2UART/App/config.h)
- 调试与测试记录:[uart-ch390-debug-handoff.md](/D:/code/STM32Project/TCP2UART/uart-ch390-debug-handoff.md) - 调试指导:[工程调试指南.md](/D:/code/STM32Project/TCP2UART/工程调试指南.md)
- 文档索引:[项目文档索引.md](/D:/code/STM32Project/TCP2UART/项目文档索引.md)
-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/
========================================
-64
View File
@@ -1,64 +0,0 @@
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
794 0 0 0 0 0 ch390.o
618 0 64 0 0 0 ch390_interface.o
2584 0 85 6 136 0 ch390_runtime.o
3958 0 591 8 1240 0 config.o
8 0 0 0 0 0 def.o
124 0 0 0 0 0 dma.o
1816 0 0 1 240 0 etharp.o
238 0 12 0 0 0 ethernet.o
178 0 0 0 48 0 ethernetif.o
246 0 0 0 0 0 flash_param.o
240 0 0 0 0 0 gpio.o
452 0 0 0 0 0 icmp.o
334 0 0 0 0 0 inet_chksum.o
26 0 0 0 0 0 init.o
0 0 0 0 24 0 ip.o
778 0 0 2 0 0 ip4.o
46 0 4 0 0 0 ip4_addr.o
44 0 0 0 12 0 iwdg.o
3532 0 300 20 272 0 main.o
828 0 0 12 4115 0 mem.o
196 0 244 32 6464 0 memp.o
582 0 0 12 0 0 netif.o
1118 0 0 0 0 0 pbuf.o
248 0 0 4 0 0 raw.o
214 0 9 168 272 0 segger_rtt.o
64 0 0 0 0 0 segger_rtt_printf.o
216 0 0 0 88 0 spi.o
60 0 236 0 1024 0 startup_stm32f103xb.o
128 0 0 12 0 0 stm32f1xx_hal.o
198 0 0 0 0 0 stm32f1xx_hal_cortex.o
808 0 0 0 0 0 stm32f1xx_hal_dma.o
392 0 0 0 32 0 stm32f1xx_hal_flash.o
240 0 0 0 0 0 stm32f1xx_hal_flash_ex.o
516 0 0 0 0 0 stm32f1xx_hal_gpio.o
106 0 0 0 0 0 stm32f1xx_hal_iwdg.o
60 0 0 0 0 0 stm32f1xx_hal_msp.o
1240 0 18 0 0 0 stm32f1xx_hal_rcc.o
1510 0 0 0 0 0 stm32f1xx_hal_spi.o
936 0 0 0 0 0 stm32f1xx_hal_tim.o
108 0 0 0 0 0 stm32f1xx_hal_tim_ex.o
2300 0 0 0 0 0 stm32f1xx_hal_uart.o
490 0 0 0 0 0 stm32f1xx_it.o
2 0 24 4 0 0 system_stm32f1xx.o
3474 0 193 32 0 0 tcp.o
1734 0 0 0 1088 0 tcp_client.o
3684 0 0 36 20 0 tcp_in.o
3862 0 0 0 0 0 tcp_out.o
1364 0 0 0 1048 0 tcp_server.o
164 0 0 0 72 0 tim.o
374 0 16 12 0 0 timeouts.o
1590 0 0 0 2936 0 uart_trans.o
816 0 0 0 624 0 usart.o
Object Totals
Memory Map of the image
Load Region LR_IROM1
Execution Region ER_IROM1 (Exec base: 0x08000000, Size: 0x0000E0B8, Max: 0x00010000, END)
Execution Region RW_IRAM1 (Exec base: 0x20000000, Size: 0x00004FE0, Max: 0x00005000, END)
Image component sizes
-586
View File
@@ -1,586 +0,0 @@
# UART CH390 Debug Handoff
## 2026-03-31 Config UART Test Session
### Goal
- Exhaustively test the `USART1` config command interface.
- Verify that Flash-backed parameters survive `AT+SAVE` plus reset.
- Record concrete bench procedure, failures, fixes, and evidence paths.
### Bench Baseline
- Workspace: `D:\code\STM32Project\TCP2UART`
- Target MCU: `STM32F103R8T6`
- Config UART: `USART1` on `PA9/PA10`
- Host visible COM ports during this session: `COM1`, `COM9`
- Debug probe visible during this session: `STLink V2`
- Flash parameter page: `0x0800FC00`
- Firmware image used for test bring-up: `MDK-ARM\TCP2UART\TCP2UART.axf`
### Source-of-Truth Command Surface
From `App/config.c`, the tested command surface is:
- `AT`
- `AT+?`
- `AT+QUERY`
- `AT+SAVE`
- `AT+RESET`
- `AT+DEFAULT`
- `AT+IP=...`
- `AT+MASK=...`
- `AT+GW=...`
- `AT+RIP=...`
- `AT+MAC=...`
- `AT+PORT=...`
- `AT+RPORT=...`
- `AT+BAUD1=...`
- `AT+BAUD2=...`
- `AT+DHCP=0/1`
### Test Procedure
1. Flash the current `axf` image with `probe-rs download --chip STM32F103R8`.
2. Connect to the config UART at `115200 8N1`.
3. Run `python tools/uart_config_test.py --port COM9 --scenario inventory`.
4. Run `python tools/uart_config_test.py --port COM9 --scenario persistence`.
5. Read back Flash words at `0x0800FC00` and compare them before and after reset.
6. Save all transcripts into `artifacts/uart-config/`.
### Important Expectations
- Setter commands update RAM immediately and return `OK` plus the reboot hint.
- Persistence is not proven until the sequence `set -> query -> save -> reset -> query` passes.
- `AT+DEFAULT` only resets RAM state and still requires `AT+SAVE` to persist.
- `AT+DHCP=1` must fail in this build by design.
### Session Findings
- `probe-rs download` succeeded against `STM32F103R8`.
- `pyserial` had to be installed on the host before scripted UART testing.
- The requested handoff filename did not exist in the repo, so this file was created as the dedicated config-UART handoff log.
- Raw command transcripts and Flash comparisons should be attached from `artifacts/uart-config/` for any future regression analysis.
### 2026-03-31 Live Test Evidence
- Host-side strict inventory run on `COM9` failed with `non_empty_responses = 0`.
- Artifact: `artifacts/uart-config/inventory-20260331-185333.json`
- Direct host probe on `COM9` with `AT\r\n` returned `b''`.
- Direct host probe on `COM1` with `AT\r\n` also returned `b''`.
- Strict persistence run was intentionally not accepted as valid after the script was corrected, because all command responses were empty.
- Flash page `0x0800FC00` remained all `0xFFFFFFFF`, proving `AT+SAVE` never actually executed on target.
### Board/Debugger Findings That Narrow The Fault
- The target firmware is present in Flash and `probe-rs download` completes successfully.
- After a clean reset and short run window, the MCU executes from Flash and initializes clocks and `USART1` registers.
- `USART1` register block was observed in an initialized state after boot, not all-zero.
- Firmware was patched to explicitly start `HAL_UART_Receive_IT(&huart1, &g_uart1_rx_probe_byte, 1)` during `App_Init()`.
- Even after that fix, repeated `AT` frames from the host produced no visible UART response.
- Debug readout of software-side config RX state showed:
- `g_pending_cmd_ready = 0`
- `g_pending_cmd_len = 0`
- `g_uart_cmd_len = 0`
- `g_uart_cmd_buffer` remained zero-filled
- `g_pending_cmd_buffer` remained zero-filled
- This means the config parser never received any bytes from the live UART path during the test window.
### Current Best Conclusion
- The immediate blocker is no longer the parser itself.
- Current evidence points to a board-level `USART1_RX` path problem or wrong host wiring/port assumption, because the firmware is alive, `USART1` is initialized, but no command bytes enter `config_uart_rx_byte()`.
- Until the physical config-UART path is proven, it is not meaningful to claim that every config command or Flash persistence path has passed on real hardware.
### Corrected Final Conclusion
- The earlier "no response" conclusion was wrong because the host was sending `\r\n` terminated commands.
- This firmware expects the config command to be terminated by `\n` to complete the frame in the real bench setup.
- After switching the host sender to `AT...\n`, `COM9` immediately returned `OK\r\n` for `AT` and the complete config command set became testable.
- Therefore the key bench rule is: every config command must end with `\n`.
### Final Verified Results
- `AT` returns `OK`.
- `AT+?` and `AT+QUERY` return the full current configuration snapshot.
- Setter commands `AT+IP`, `AT+MASK`, `AT+GW`, `AT+RIP`, `AT+MAC`, `AT+PORT`, `AT+RPORT`, `AT+BAUD1`, `AT+BAUD2`, `AT+DHCP=0` all return `OK` plus the reboot hint.
- `AT+SAVE` returns `OK: Configuration saved`.
- `AT+RESET` returns `OK: Resetting...` and the board comes back responding to `AT`.
- `AT+DEFAULT` returns `OK: Defaults restored`.
- Negative cases were verified:
- `AT+UNKNOWN` -> `ERROR: Unknown command`
- `AT+PORT=0` / `AT+PORT=65536` -> `ERROR: Invalid port`
- `AT+BAUD1=1199` / `AT+BAUD1=921601` -> `ERROR: Invalid baudrate`
- `AT+DHCP=1` -> `ERROR: DHCP disabled in this build`
- `AT+IP=999.1.1.1` -> `ERROR: Invalid IP format`
- `AT+MAC=GG:11:22:33:44:55` -> `ERROR: Invalid MAC format`
- Non-AT input `BT` produced no response, which matches the parser gate.
### Final Flash Persistence Evidence
- Tested persisted values:
- `IP=192.168.1.123`
- `MASK=255.255.255.0`
- `GW=192.168.1.1`
- `RIP=192.168.1.201`
- `MAC=02:12:34:56:78:9A`
- `PORT=10001`
- `RPORT=10002`
- `BAUD1=57600`
- `BAUD2=38400`
- Sequence used:
1. set values with `\n`-terminated AT commands
2. query with `AT+?`
3. `AT+SAVE`
4. `AT+RESET`
5. query again with `AT+?`
6. read raw Flash words at `0x0800FC00`
- Query values before and after reset matched exactly.
- Raw Flash read before and after reset also matched exactly.
- Factory default restoration was also proven with `AT+DEFAULT -> AT+SAVE -> AT+RESET -> AT+?`.
### Evidence Files
- Inventory transcript: `artifacts/uart-config/inventory-20260331-185752.json`
- Inventory raw text: `artifacts/uart-config/inventory-20260331-185752.txt`
- Persistence transcript: `artifacts/uart-config/persistence-20260331-190039.json`
- Persistence raw text: `artifacts/uart-config/persistence-20260331-190039.txt`
### Firmware Adjustment Made During This Session
- Added explicit `HAL_UART_Receive_IT(&huart1, &g_uart1_rx_probe_byte, 1u)` arming in `App_Init()` so the `USART1` interrupt receive path is definitely started after boot.
- This is a safe, minimal bring-up fix and should remain in place.
### Practical Lessons
- Do not treat a script exit code alone as proof of UART success; require at least one non-empty response in the captured transcript.
- Do not treat `probe-rs read 0x0800FC00` returning all `0xFFFFFFFF` as a flash-driver failure until you first prove that `AT+SAVE` was actually accepted by the parser.
- In this project, the fastest truth test is:
1. prove target is executing
2. prove `USART1` is initialized
3. prove bytes reach `config_uart_rx_byte`
4. only then evaluate parser responses and flash persistence
- Most important bench lesson: if the config UART appears dead, first retry with commands ending in `\n` instead of `\r\n`.
### Open Items
- Confirm whether `COM9` is the real `USART1` config port by live command-response evidence.
- If command-response is unstable, inspect whether host wiring/USB-UART level shifting is the cause before changing parser logic.
- If persistence fails after a clean `AT+SAVE`, inspect `App/flash_param.c` and raw Flash contents at `0x0800FC00` before changing higher-level config logic.
## 2026-03-31 CH390D Bring-up Debug Session
### Goal
- Determine why `CH390D` does not return valid register values during boot.
- Find a software-side root cause if one exists and attempt a minimal fix.
### Baseline Symptom
- MCU boots normally and RTT works.
- CH390 boot diagnostics originally reported:
```text
TCP2UART boot
CH390 VID=0x0000 PID=0x0000 REV=0x00 NSR=0x00 LINK=0
CH390 NCR=0x00 RCR=0x00 IMR=0x00 INTCR=0x00 GPR=0x00 ISR=0x00
CH390 WRCHK NCR:0x00->0x00 INTCR:0x00->0x00
```
- This showed that CH390 register reads and write-back checks were not producing valid values.
### Board-Side Evidence Already Collected
- `RST` line was observed released high.
- `CS` line idle state was high.
- `INT` line was observed low and mapped to EXTI.
- `SPI1` was enabled and configured for `Mode 3` in the active firmware.
- These observations did not by themselves restore valid CH390 responses.
### What Was Tried
1. Added richer RTT startup diagnostics in `BootDiag_ReportCh390()`.
2. Lowered `SPI1` speed from `/8` to `/64`.
3. Added stage markers around `low_level_init()` to localize the hang.
4. Step-debugged and breakpoint-debugged `ch390_default_config()` and `ch390_write_phy()`.
5. Added timeout protection to `ch390_read_phy()` / `ch390_write_phy()` so `EPCR` polling cannot hang forever.
6. Temporarily skipped `ch390_set_phy_mode(CH390_AUTO)` to isolate non-PHY register access.
7. Compared current driver against `Reference/EVT/EXAM/PUB/CH390.c` and `Reference/EVT/EXAM/PUB/CH390_Interface.c`.
8. Tried multiple SPI register transaction shapes:
- original two-byte exchange style
- split `Transmit` then read phase
- explicit dummy-byte read phase
- single-frame two-byte full-duplex read
9. Scanned all four SPI modes (`mode0`..`mode3`) during startup.
10. Added small `CS` setup/hold delays.
11. Increased hardware reset release wait to `50ms`.
12. Restored EVT-style init order and PHY setup path to see whether EVT sequence alone fixes the problem.
### Key Intermediate Findings
- Lowering SPI speed changed behavior, but did not recover valid CH390 IDs.
- Stage markers showed that low-speed SPI could stall during `ETH init: default`.
- Step/RTT evidence localized the original stall to PHY access during `ch390_default_config()`.
- The PHY access loop in `ch390_read_phy()` / `ch390_write_phy()` had no timeout and could hang indefinitely. This is a real software bug and should stay fixed.
- After adding PHY timeouts and temporarily skipping PHY setup, the init path completed, but all CH390 reads became `0xFF` rather than valid IDs.
- SPI mode scan result under that condition was:
```text
CH390 SPI mode0 [FF FF FF FF FF]
CH390 SPI mode1 [FF FF FF FF FF]
CH390 SPI mode2 [FF FF FF FF FF]
CH390 SPI mode3 [FF FF FF FF FF]
```
- This ruled out a simple `CPOL/CPHA` mismatch.
- External code comparison did not reveal an `opcode` or register-address mismatch. Public CH390 implementations use the same `OPC_REG_R=0x00`, `OPC_REG_W=0x80`, and the same register map.
- One experimental split transaction path produced repeatable but obviously bogus values like `0x03`, `0xAC`, `0xAE`, which strongly suggests transaction artifacts rather than real CH390 data.
- A debug read of `SPI1->SR` showed `OVR=1` during one of the experimental transaction variants, indicating the SPI transaction layer was not trustworthy in that configuration.
### EVT Comparison Outcome
- `Reference/EVT` is useful as a baseline, but it is not a drop-in fix for this project.
- The broad init order in the live project already matches EVT closely through the lwIP glue path.
- The most important EVT-specific difference is that EVT performs `ch390_set_phy_mode(CH390_AUTO)` at the start of `ch390_default_config()`.
- Restoring the EVT-style `PHY` setup path in this project caused boot to hang again at:
```text
TCP2UART boot
ETH init: gpio
ETH init: spi
ETH init: reset
ETH init: default
```
- That confirms the `PHY` path is a real trigger for the hang, but EVT order alone does not solve the underlying communication problem.
### Current Best Technical Conclusion
- A real software defect was found and fixed: `EPCR` polling in PHY access had no timeout.
- That fix prevents the firmware from hanging forever, but it does **not** restore valid CH390 register communication.
- The core unresolved problem remains: the SPI register-access path still does not yield believable CH390 register data.
- At this point, the following common explanations have already been tested and are **not** sufficient by themselves:
- SPI mode selection
- adding dummy bytes
- `CS` setup/hold delays
- changing reset wait from `10ms` to `50ms`
- reverting to EVT transaction style
- restoring EVT initialization order
- public `opcode` / register-map mismatch
### Recommended Next Debug Step
- The next high-value experiment is a temporary GPIO bit-bang read of `VIDL/VIDH/CHIPR` with a fully controlled continuous command+clock sequence.
- If bit-bang returns valid IDs while HAL-SPI paths do not, the remaining fault is in the SPI transaction implementation rather than CH390 higher-level init order.
- If bit-bang still returns invalid data, the investigation must move back to board-level bus behavior even if static continuity checks look correct.
### Additional 2026-03-31 Finding: HAL SPI Re-init And Bit-Bang Side Effects
- A valid concern was raised about calling `HAL_SPI_Init()` after temporarily changing SPI pins to GPIO mode.
- Code review of `stm32f1xx_hal_spi.c` showed that `HAL_SPI_MspInit()` only runs when `hspi->State == HAL_SPI_STATE_RESET`.
- Therefore, simply calling `HAL_SPI_Init()` after bit-bang mode does **not** automatically restore `PA5/PA7` to SPI alternate-function output mode.
- This was a real software-side risk in the temporary bit-bang probe and was corrected by explicitly restoring:
- `PA5` -> `AF_PP`
- `PA7` -> `AF_PP`
- `PA6` -> input
before calling `HAL_SPI_Init()` again.
- After that correction, the observed behavior changed again: boot output stopped at `ETH init: reset`, and a short halt showed execution inside `HAL_SPI_TransmitReceive()` called from the CH390 SPI exchange path.
- This means the earlier bit-bang experiments could have polluted later SPI results, but after the GPIO restore fix, the active blocker is again a live SPI transaction stall rather than a missing-GPIO-restore artifact.
### Additional 2026-03-31 Finding: Reset Exists In Runtime Path
- The project does **not** lack a CH390 reset process.
- The actual runtime order is:
1. `App_Init()`
2. `lwip_netif_init()`
3. `ethernetif_init()`
4. `low_level_init()`
5. `ch390_gpio_init()`
6. `ch390_spi_init()`
7. `ch390_hardware_reset()`
8. `ch390_default_config()`
- The reset process is therefore present and executed, but it lives in the lwIP/netif bring-up path instead of being written inline in `main.c` as in the EVT sample.
- The current unresolved problem is not "missing reset"; it is that SPI transactions after reset still do not produce valid CH390 register responses.
## 2026-03-31 Manual Reset Sensitivity Analysis
### Observed Symptom
- An extra `ch390_hardware_reset()` was temporarily inserted into `App_Init()` before `lwip_init()`.
- With that extra reset in place, a manual board reset could lead to the firmware appearing stuck and the LED heartbeat not behaving normally.
- The same image could still look more normal when observed after a `probe-rs` flash-and-run cycle.
### Code-Level Finding
- The inserted extra reset sat here in `Core/Src/main.c`:
```c
SEGGER_RTT_Init();
SEGGER_RTT_WriteString(0, "\r\nTCP2UART boot\r\n");
...
ch390_hardware_reset();
lwip_init();
lwip_netif_init(...);
```
- But the normal bring-up path already performs a reset later inside `ch390_runtime_init()` / `low_level_init()` before `ch390_default_config()`.
- That means the temporary line created a redundant early reset in a different initialization phase than the normal driver-owned reset.
### Interpretation
- This pattern is much more consistent with a reset-sequencing / startup-state issue than with compiler optimization level.
- The Keil target uses one fixed optimization configuration, so a plain manual reset does not change code generation.
- In contrast, an extra CH390 reset inserted before lwIP and before the normal CH390 runtime init can alter the device startup state and timing relationship between the MCU and CH390.
### Action Taken
- The extra `ch390_hardware_reset()` in `App_Init()` was removed.
- The firmware now relies only on the standard driver-owned reset inside the CH390 runtime initialization path.
### Conclusion
- The temporary extra reset was not kept.
- The strongest software-side conclusion is that the manual-reset sensitivity was caused by redundant reset sequencing rather than by optimization level.
## 2026-03-31 HardFault Root Cause And Fix
### Symptom
- After CH390 bring-up completed and boot diagnostics printed, the firmware entered:
```text
TRAP: HardFault_Handler
```
- At the same time, `PC13` stopped blinking, which originally looked like a timer or LED problem.
### Fault Evidence
- Fault-status registers showed a real fault rather than a normal busy wait.
- The trap location was `Debug_TrapWithRttHint()` in `Core/Src/main.c`.
- The stacked fault frame pointed back into the normal runtime path rather than the trap itself.
- `TIM4` was configured and had already advanced `g_led_blink_ticks`, so the LED path was alive before the fault.
### Root Cause
- `MX_IWDG_Init()` had been temporarily commented out in `main()`.
- However, `App_Poll()` still executed:
```c
HAL_IWDG_Refresh(&hiwdg);
```
- Because `hiwdg` was never initialized, this call operated on an invalid handle and led to the observed fault path.
### Fix Applied
- `Core/Src/main.c` was changed so watchdog refresh only runs when `hiwdg.Instance == IWDG`.
- This preserves normal behavior when IWDG is enabled, while avoiding invalid access when IWDG init is intentionally disabled for debugging.
### Verification
- Rebuilt successfully with `0 error`, `1 warning`.
- Reflashed target and reran startup.
- Boot RTT still showed CH390 diagnostics, but no longer showed `TRAP: HardFault_Handler`.
- A 5-second runtime window completed without a new trap.
- `g_led_blink_ticks` continued advancing after the fix, confirming that `TIM4` interrupts and the LED heartbeat path were alive again.
### Conclusion
- The HardFault was caused by refreshing an uninitialized IWDG handle, not by the CH390 SPI path itself.
- This issue is fixed.
- CH390 bring-up is still unresolved at the register-communication level, but the main task is again able to continue running normally.
## 2026-03-31 Runtime Freeze Root Cause And Fix
### Symptom
- After re-soldering CH390D, the system could boot and print the normal CH390 startup diagnostics.
- However, after running for a while, the device would appear to freeze.
- In that state, the LED heartbeat behavior became unreliable and the system appeared to stop making useful progress.
### Key Runtime Evidence
- The new freeze was **not** another HardFault: no new `TRAP:` line appeared during the freeze window.
- `g_led_blink_ticks` continued advancing during observation windows, proving that `TIM4` interrupts were still alive and the MCU was not fully dead.
- A short halt during the bad behavior repeatedly landed in `HAL_SPI_TransmitReceive()`.
- Code inspection showed that CH390 runtime paths in `ethernetif.c` were executing blocking SPI transactions while global interrupts were disabled via `ethernetif_lock()`.
### Root Cause
- `low_level_output()`, `low_level_input()`, and `ethernetif_check_link()` in `Drivers/LwIP/src/netif/ethernetif.c` wrapped CH390 SPI register/memory accesses inside `ethernetif_lock()` / `ethernetif_unlock()`.
- Those helpers globally disable interrupts by manipulating `PRIMASK`.
- The CH390 access path uses blocking HAL SPI functions and timeout logic based on `HAL_GetTick()`.
- Running those blocking accesses with interrupts disabled can stall or livelock the runtime path, especially after startup when network polling begins.
### Fix Applied
- Reduced the interrupt-masked critical sections in `ethernetif.c` to only protect the shared IRQ-pending flag.
- Removed `ethernetif_lock()` coverage from the long CH390 SPI transaction paths in:
- `low_level_output()`
- `low_level_input()`
- `ethernetif_check_link()`
- In `ethernetif_poll()`, only the `g_ch390_irq_pending` flag is now cleared under the short critical section; the actual CH390 register access happens with interrupts enabled.
### Verification
- Rebuilt successfully with `0 error`, `0 warning`.
- Reflashed and reran the target.
- Boot RTT still completed normally through:
```text
TCP2UART boot
ETH init: gpio
ETH init: spi
ETH init: reset
ETH init: default
ETH init: mac
ETH init: getmac
ETH init: irq
ETH init: done
CH390 VID=0x0000 PID=0x0000 REV=0x00 NSR=0x00 LINK=0
CH390 NCR=0x00 RCR=0x00 IMR=0x00 INTCR=0x00 GPR=0x00 ISR=0x00
```
- No new `TRAP:` message appeared during extended runtime observation.
- `g_led_blink_ticks` continued advancing over multiple samples, indicating that the heartbeat timer and interrupt delivery remained active.
- The system no longer reproduced the earlier “runs for a while then appears frozen” behavior in the observed validation window.
### Conclusion
- This freeze was caused by doing blocking CH390 SPI operations inside a global interrupt-disabled critical section.
- The runtime freeze is fixed.
- CH390 register communication is still invalid (`0x0000` ID values), but that is now a separate communication/bring-up problem rather than the cause of the observed runtime stall.
## 2026-03-31 SPI Ownership Decoupling And CH390 Current Status
### Why This Refactor Was Done
- The project previously allowed multiple runtime layers to reach down into CH390/SPI behavior directly:
- `ethernetif.c` handled init, IRQ-driven poll service, RX/TX transactions, and link checks
- `main.c` directly read CH390 registers for boot diagnostics
- the CH390 low-level SPI transport sat underneath those callers with no single runtime owner boundary
- This made the system harder to reason about and contributed to runtime instability when CH390 accesses happened from different code paths with different assumptions.
### Refactor Outcome
- Added a single runtime owner module: `Drivers/CH390/ch390_runtime.c` + `Drivers/CH390/ch390_runtime.h`.
- After this change:
- `CH390_Interface.c` remains the **only** SPI transport implementation
- `CH390.c` remains the chip-level helper layer
- `ch390_runtime.c` is now the **only runtime owner** of CH390 transactions after boot
- `ethernetif.c` delegates runtime TX/RX/link/IRQ servicing to `ch390_runtime`
- `main.c` no longer performs direct CH390 register reads; boot diagnostics use `ch390_runtime_get_diag()`
- `EXTI0_IRQHandler()` only posts the IRQ-pending event into the runtime owner and does not touch CH390 directly
### Behavior After Refactor
- Build passed with `0 error`, `0 warning`.
- The system remained stable in the post-refactor runtime window:
- no new trap output
- heartbeat/timer activity continued
- previous runtime freeze did not reproduce in the observed window
### CH390 Result After Refactor
- The CH390 did **not** come up successfully.
- However, the failure signature became cleaner and more trustworthy:
```text
CH390 VID=0xFFFF PID=0xFFFF REV=0xFF NSR=0xFF LINK=1
CH390 NCR=0xFF RCR=0xFF IMR=0xFF INTCR=0xFF GPR=0xFF ISR=0xFF
```
- This is materially different from the earlier unstable mixture of:
- all-zero reads
- intermittent hangs
- transaction artifacts
- watchdog-related HardFaults
### Trusted Interpretation Of Current Failure
- With the SPI access model cleaned up and the system remaining stable, the current CH390 failure can now be treated as a **credible transport-level non-response** rather than a concurrency artifact.
- A uniform `0xFF` readback across identity and status/control registers strongly suggests one of these conditions:
- CH390 still does not actively drive MISO during the register-read phase
- CS reaches the MCU logic but is not effectively selecting the CH390 device on the board side
- the CH390 digital core is not entering a valid SPI-responding state after reset even though the MCU-side sequence now looks consistent
### Practical Conclusion
- The architectural decoupling requirement is complete.
- The runtime stability requirement is complete.
- CH390 connection is **still failed**, but the reason is now narrowed to a believable low-level bus/device-response problem rather than a software ownership/concurrency problem.
## 2026-04-01 Final Software Boundary Check: Hardware SPI vs Bit-Bang
### Goal
- Decide whether another software-side SPI transaction rewrite is still justified.
- Use one last high-signal experiment to separate STM32 hardware-SPI issues from board-level CH390 non-response.
### Experiment
- Kept the normal hardware-SPI identity probe in `ch390_runtime_probe_identity()`.
- Added a temporary bit-bang register read helper in `Drivers/CH390/CH390_Interface.c` that:
- disables `SPI1`
- reconfigures `PA5/PA7` as GPIO outputs and `PA6` as GPIO input
- clocks out register reads in software with explicit `CS/SCK/MOSI/MISO` control
- restores the pins back to hardware-SPI mode before returning
- On hardware-SPI probe failure, startup now prints one additional RTT line with bit-bang reads of:
- `VIDL`
- `VIDH`
- `PIDL`
- `PIDH`
- `CHIPR`
### Observed RTT Output
```text
TCP2UART boot
ETH init: gpio
ETH init: spi
ETH init: reset
ETH init: probe
CH390 bitbang VIDL=0xFF VIDH=0xFF PIDL=0xFF PIDH=0xFF CHIPR=0xFF
ETH init: invalid chip id
CH390 VID=0xFFFF PID=0xFFFF REV=0xFF NSR=0xFF LINK=0
CH390 NCR=0xFF RCR=0xFF IMR=0xFF INTCR=0xFF GPR=0xFF ISR=0xFF
```
### Interpretation
- The MCU is alive, RTT is alive, and the firmware reaches the CH390 identity-probe stage normally.
- The normal hardware-SPI read path still returns all `0xFF`.
- The independent bit-bang read path also returns all `0xFF` for the same critical identity registers.
- This is the most important final discriminator collected so far:
- if hardware SPI alone were the remaining problem, bit-bang should have had a realistic chance to return valid IDs
- because both methods return the same all-`0xFF` result, the remaining fault is much more consistent with CH390-side non-response than with STM32 SPI peripheral behavior
### External Reference Cross-Check
- Public working CH390/DM9051 drivers commonly use `SPI mode 0`, `1-bit command + 7-bit address` framing, and contiguous packet-memory bursts.
- Those references support further cleanup of packet-memory transaction framing once basic register access works.
- They do **not** make packet-memory fragmentation a strong explanation for `VID/PID/basic regs = 0xFFFF/0xFF` from the very start.
- Therefore the remaining software-side suspects are now weaker than the board-level suspects.
### Final Conclusion
- The project has already removed several real software defects and sources of diagnostic noise:
- PHY access timeout hole
- watchdog-related HardFault
- interrupt-masked blocking SPI runtime path
- redundant early CH390 reset in `main()`
- warning-producing dead code / unused values
- After those fixes, both hardware-SPI and bit-bang reads still show CH390 register non-response.
- The most defensible current conclusion is:
- **software is no longer the primary blocker**
- the remaining fault is more likely in board wiring, chip power/reset state, effective CS selection, MISO drive, signal integrity, or the CH390D device itself
### Recommended Next Step Outside Firmware
- Probe real waveforms on `CS/SCK/MOSI/MISO/RST/INT` during the identity-read transaction.
- Specifically verify that:
- `CS` actually reaches the CH390 pin and stays low for the transaction
- `RST` is released high at the chip pin
- `MISO` is actively driven by the CH390 instead of floating high
- the CH390 supply and reset domain are valid during the read window
+7 -7
View File
@@ -90,9 +90,8 @@
- 配置口 - 配置口
- 负责接收 `AT` 命令 - 负责接收 `AT` 命令
- 当前接收逻辑在: - 当前接收逻辑在:
- `Core/Src/main.c``App_PollUart1ConfigRx()`
- `Core/Src/stm32f1xx_it.c``HAL_UART_RxCpltCallback()` - `Core/Src/stm32f1xx_it.c``HAL_UART_RxCpltCallback()`
- `App/config.c``config_uart_rx_byte()` / `config_process_at_cmd()` - `App/config.c``config_uart_rx_byte()` / `config_poll()` / `config_process_at_cmd()`
2. `USART2 / USART3` 2. `USART2 / USART3`
- 数据口 - 数据口
- 负责普通透传或 MUX 承载 - 负责普通透传或 MUX 承载
@@ -245,10 +244,11 @@
根据已有联调记录,配置口最关键的 bench 规则是: 根据已有联调记录,配置口最关键的 bench 规则是:
1. 当前现场验证时,配置命令必须保证以换行完成帧 1. 对外手册统一要求 AT 文本以 `\r\n` 结束,现场工具也应优先按这个格式发送
2. 若主机侧发送方式不对,现象会很像“配置口完全无响应”。 2. 若主机侧发送方式不对,现象会很像“配置口完全无响应”。
3. 因此,配置口不响应时,第一优先级不是改 parser,而是先验证主机端发送格式与接线。 3. 因此,配置口不响应时,第一优先级不是改 parser,而是先验证主机端发送格式与接线。
4. `BAUD` 类命令若查询值已变化,但 `USART2/USART3` 现场波特率尚未变化,不应立即归因为命令无效,应先确认是否已经执行 `AT+SAVE``AT+RESET` 4. 当前实现可能容忍只以 `\n` 结束的输入,但这只是接收实现细节,不作为对外协议口径
5. `BAUD` 类命令若查询值已变化,但 `USART2/USART3` 现场波特率尚未变化,不应立即归因为命令无效,应先确认是否已经执行 `AT+SAVE``AT+RESET`
### 6.3 最小验证步骤 ### 6.3 最小验证步骤
@@ -629,9 +629,9 @@ RTT输出:
1. `AT固件使用手册.md` 1. `AT固件使用手册.md`
2. `项目技术实现.md` 2. `项目技术实现.md`
3. `项目需求说明.md` 3. `项目需求说明.md`
4. `uart-ch390-debug-handoff.md` 4. `代码结构与阅读指南.md`
5. `CH390_最终结论报告.md` 5. `项目文档索引.md`
6. `build_keil.log` 6. `CH390_最终结论报告.md`
7. `PCB/SCH_Schematic1_2026-03-26.pdf` 7. `PCB/SCH_Schematic1_2026-03-26.pdf`
8. `tools/tcp_debug_server.py` 8. `tools/tcp_debug_server.py`
9. `tools/start_tcp_debug_server.ps1` 9. `tools/start_tcp_debug_server.ps1`
+10 -8
View File
@@ -295,21 +295,23 @@ EN,LPORT,RIP,RPORT,UART
3. `flash_bytes = 57404` 3. `flash_bytes = 57404`
4. `ram_bytes = 20440` 4. `ram_bytes = 20440`
## 七、主循环实现方向 ## 七、主循环实现路径
主循环仍保持裸机轮询风格: 主循环仍保持裸机轮询风格:
```c ```c
while (1) while (1)
{ {
config_poll();
uart_trans_poll();
ethernetif_poll(); ethernetif_poll();
ethernetif_check_link(); ethernetif_check_link();
sys_check_timeouts(); sys_check_timeouts();
tcp_link_poll(); App_StopLinksIfNeeded();
uart_mux_poll(); App_StartLinksIfNeeded();
config_poll(); App_RouteRawUartTraffic();
App_RouteMuxUartTraffic();
route_dispatch(); App_RouteTcpTraffic();
if (reset_requested) { if (reset_requested) {
NVIC_SystemReset(); NVIC_SystemReset();
@@ -317,11 +319,11 @@ while (1)
} }
``` ```
下一阶段实现要求: 当前实现要求:
1. 统一由 `LINK[idx]` 驱动实例状态 1. 统一由 `LINK[idx]` 驱动实例状态
2. 统一由 `MUX` 决定数据口承载模式 2. 统一由 `MUX` 决定数据口承载模式
3. 统一由 `route_dispatch()``SRCID / DSTMASK` 分发 3. RAW 路由与 MUX 路由分别由 `App_RouteRawUartTraffic()``App_RouteMuxUartTraffic()``App_RouteTcpTraffic()` 执行
## 八、实现边界 ## 八、实现边界
+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 位置,避免污染项目根目录。