# STM32F4-Base

> **⚠️ 编译验证：所有代码修改后必须执行 `@build` 验证编译通过（0 错误 0 警告），否则不要提交。**

## 项目概述

STM32F407ZGTx (Cortex-M4 FPU) 基础固件项目，集成 CH395F 以太网控制器 SPI 驱动、GD5F2GQ5UE SPI NAND Flash 存储（含 FlashDB KVDB/TSDB 数据库）和 TPAFE5160 16位8通道同步采样 ADC 并行接口驱动。

## 目录结构

```
STM32F4-Base/
├── Src/                        # CubeMX 生成的外设初始化 + main
├── Inc/                        # CubeMX 生成的头文件
├── Drivers/
│   ├── BSP/
│   │   ├── CH395F/             # CH395F 以太网芯片驱动（手写）
│   │   ├── NET/                # BSD Socket API 网络抽象层（手写）
│   │   │   ├── net_config.h    # 网络配置宏定义
│   │   │   ├── net_types.h     # 类型定义、地址结构、Socket 控制块
│   │   │   ├── net_socket.h    # BSD Socket API 头文件
│   │   │   ├── net_socket.c    # API 核心实现 + 状态机
│   │   │   ├── net_select.h    # select/poll API 头文件
│   │   │   └── net_select.c    # select/poll 实现
│   │   ├── GD5F2GQ5UE/        # GD5F2GQ5UE NAND Flash 驱动（手写）
│   │   │   ├── gd5f2gq5ue.h/c         # 底层 SPI 驱动
│   │   │   ├── fal_flash_gd5f2gq5ue.c  # FAL 设备适配层
│   │   │   ├── fal_cfg.h               # FAL 设备表 + 分区表
│   │   │   └── fdb_cfg.h               # FlashDB 功能配置
│   │   └── TPAFE5160/          # TPAFE5160 ADC 并行接口驱动（手写）
│   ├── STM32F4xx_HAL_Driver/  # ST HAL 库（CubeMX 生成）
│   └── CMSIS/                 # ARM CMSIS（CubeMX 生成）
├── Lib/
│   └── FlashDB/               # FlashDB 数据库库（v2.2.99）
│       ├── src/               # FlashDB 核心源码
│       ├── inc/               # FlashDB 头文件
│       └── port/fal/          # FAL 抽象层
├── MDK-ARM/                   # Keil MDK 工程文件
├── docs/                      # 参考文档
└── STM32F407-Demo.ioc         # STM32CubeMX 项目源文件
```

## 关键文件

| 路径 | 说明 |
|---|---|
| `Src/main.c` | 程序入口，初始化序列及主循环 |
| `Drivers/BSP/CH395F/ch395f.c/h` | CH395F 以太网芯片 SPI 驱动 |
| `Drivers/BSP/NET/net_socket.c/h` | BSD Socket API 网络抽象层 |
| `Drivers/BSP/NET/net_select.c/h` | select/poll I/O 多路复用 |
| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c/h` | GD5F2GQ5UE NAND Flash SPI 驱动 |
| `Drivers/BSP/GD5F2GQ5UE/fal_flash_gd5f2gq5ue.c` | FAL 设备适配层 |
| `Drivers/BSP/TPAFE5160/tpafe5160.c/h` | TPAFE5160 ADC 并行接口驱动 |
| `Src/freertos.c` | FreeRTOS task 创建和任务函数（CubeMX 生成 + 手写） |
| `Inc/FreeRTOSConfig.h` | FreeRTOS 内核配置 |
| `MDK-ARM/STM32F407-Demo.uvprojx` | Keil MDK 项目文件 |
| `STM32F407-Demo.ioc` | STM32CubeMX 项目源文件 |

## 构建

仅支持 Keil MDK-ARM v5 (ARMCC)。打开 `MDK-ARM/STM32F407-Demo.uvprojx` 编译。

- 编译器：ARMCC V5.06 update 7，优化 `-O4`（`spi.c`/`usart.c`/HAL 源文件 `-O0`），C99，`USE_HAL_DRIVER, STM32F407xx`

### 命令行编译（Agent 使用）

修改代码后必须执行命令行编译验证，**仅使用** `@build`，不得直接调用 UV4.exe：

```
@build
```

该命令定义在 `.opencode/command/build.md`，自动执行 `MDK-ARM/build.bat` 进行全量编译（`-r`）。

**退出码：** 0 = 成功（0 Error, 0 Warning）| 1 = 有警告（同样不通过）| 2+ = 错误，需查看 `MDK-ARM/build_log.txt` 定位

## 硬件配置

- **主频：** HSE 25MHz → PLL 168MHz (4/168/2)
- **6 个 LED：** PC4, PC5, PB1, PB2, PF11, PF12（低电平点亮）
- **CH395F：** SPI2 (PB12 CS, PB13 SCK, PB14 SDO, PB15 SDI)
- **GD5F2GQ5UE：** SPI1 (PE0 CS, PB3 SCK, PB4 MISO, PB5 MOSI, PB8 WP, PE1 HOLD)
- **TPAFE5160：** 并行16位 (PG0-PG15 数据, PD3 RD, PD4 CONVST, PD7 BUSY, PD1 FRSTDATA, PF13-15 OS[2:0])
- **USART1：** PA9 TX, PA10 RX (115200bps)

## 启动顺序

```
HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init() → MX_SPI2_Init() → MX_SPI1_Init()
→ gd5f2gq5ue_init() → fdb_kvdb_init() → [USER CODE: net_init + TCP listen + 其他外设初始化]
→ osKernelInitialize() → MX_FREERTOS_Init() → osKernelStart()  ← FreeRTOS 调度器启动
→ [FreeRTOS tasks: defaultTask, netTask 运行]
```

## 代码规范

参考 `嵌入式C语言代码规范（V1.0）.md`，关键要点：

- 缩进：4 空格，禁止 Tab
- 命名：小写字母+下划线；全局变量 `g_` 前缀，静态 `s_`，指针 `p_`，数组 `a_`
- 函数注释块需包含：函数功能、入口参数、返回值、限定条件、函数说明
- 大括号：K&R 风格（左大括号不换行）
- 文件头注释：模块名称、功能、平台、作者、日期、修改记录
- 头文件保护宏：`__MODULE_NAME_H` 格式，带 `extern "C"`

## 注意

- `Inc/` 和 `Src/` 中 CubeMX 生成的文件带有 `USER CODE BEGIN`/`END` 标记，自定义代码应写在这些区域之间
- `ch395f.c/h`、`gd5f2gq5ue.c/h`、`fal_flash_gd5f2gq5ue.c` 为纯手工代码，不受 CubeMX 保护
- `tpafe5160.c/h` 为纯手工代码，不受 CubeMX 保护
- CH395F 每次 SPI 事务需调用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹
- GD5F2GQ5UE 的 `gd5f2gq5ue.c` 中声明了 `extern SPI_HandleTypeDef hspi1`，需确保 SPI1 已初始化
- FlashDB 使用 FAL 模式，`fdb_cfg.h` 中定义 `FDB_USING_FAL_MODE`，`fal_cfg.h` 中定义分区表
- FlashDB 详细使用说明见 `FlashDB使用说明.md`
- `sd2506.c/h` 为纯手工代码，不受 CubeMX 保护
- FreeRTOS Kernel V10.3.1 via CMSIS-RTOS V2 接口，HAL 时基使用 TIM7（非 SysTick，避免与 FreeRTOS 冲突）
- NVIC 优先级分组 4 位，外设中断优先级全部 ≥ `configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`（5），确保 ISR 可调 FreeRTOS API
- 网络处理在 `netTask` 中运行，`net_poll()` 每 10ms 调用一次，TCP echo 在 netTask 中完成
- `freeertos.c` 中 `USER CODE` 区域可添加自定义 task、mutex、semaphore、queue

## CH395F 驱动关键点

驱动初始化顺序、SPI/DMA 通信、TCP/UDP 参数配置、Socket 缓冲区分配等完整关键点见 `docs/CH395F_Trap_Records.md` → 附：CH395F 驱动关键点。

## 测试说明

- **测试代码文件**：`Drivers/BSP/CH395F/ch395f_test.h` / `ch395f_test.c`
- **PC 端测试脚本**：`test/ch395f_socket_test.py`，支持 Python 3.7+
- **调用入口**：`Src/main.c` 的 `USER CODE BEGIN 2` 区域
- **Phase 1**（9 项寄存器测试）：✅ 通过
- **Phase 2**（TCP Client 收发 + 关闭重连 ×3 轮）：✅ 通过
- **Phase 3**（UDP Server Echo + PING + 大包）：✅ 通过
- **Phase 4**（NET 层 TCP Echo）：✅ 通过（netTask 中永久运行）
- **Phase 5**（NET 层 UDP Echo）：✅ 通过
- **Phase 6**（DHCP 自动获取 IP）：✅ 通过（MCU 广播宣告 + PC 监听发现）
- **Phase 7**（Select/Poll I/O 多路复用）：✅ 通过
- **Phase 8**（多客户端 7 并发）：✅ 通过，成功率 100%
- **测试拓扑**：PC (192.168.1.2) ↔ 交换机 ↔ CH395F (192.168.1.100)
- **详细使用说明**：见 `docs/CH395F_Test_Guide.md`

## GD5F2GQ5UE 驱动关键点

- SPI Mode 0（CPOL=0, CPHA=0），时钟 42MHz
- 初始化必须按顺序：复位 → 读 ID → 使能 ECC（B0h=10h）→ 解除块保护（A0h=00h）
- SET_FEATURE 命令前必须先发写使能（06h）
- 块擦除（D8h）参数是字节地址（块编号 × 128KB），不是块编号
- 读取 ID（9Fh）返回 3 字节，第 0 字节无意义，第 1 字节 MID，第 2 字节 DID

## Flash 分区规划（256MB GD5F2GQ5UE）

| 分区名 | 偏移 | 大小 | 块范围 | 用途 |
|--------|------|------|--------|------|
| fdb_kvdb1 | 0 | 64MB | Block 0~511 | FlashDB KVDB 键值数据库 |
| fdb_tsdb1 | 64MB | 64MB | Block 512~1023 | FlashDB TSDB 时序数据库 |
| ftl_fatfs | 128MB | 128MB | Block 1024~2047 | dhara FTL + FatFS 文件系统 |

- FlashDB 通过 FAL 层访问 `fdb_kvdb1` / `fdb_tsdb1`（`Drivers/BSP/GD5F2GQ5UE/fal_cfg.h`）
- FatFS 通过 dhara FTL 访问 `ftl_fatfs` 分区，`nand_ftl.c` 中 `FTL_START_BLOCK = FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE`（与 `fal_cfg.h` 联动）
- 三个分区物理隔离，互不重叠，分区配置统一在 `fal_cfg.h:25-30`

## TPAFE5160 驱动关键点

- AD7606 P2P 兼容替代品，并行接口协议一致
- 并行模式：CS 接地（始终选中），PAR/SER/BYTE SEL 接 GND（并行），DB15/BYTE SEL 接 GND（非字节模式）
- 数据总线 DB[15:0] 接 GPIOG[15:0]，通过 `(uint16_t)GPIOG->IDR` 一次读取16位
- CONVST 上升沿触发全部8通道同步采样，BUSY 高电平表示转换中
- RD 下降沿输出通道数据，按通道1~8顺序依次输出
- FRSTDATA 在第一个 RD 下降沿变高，指示通道1数据就绪
- 过采样 OS[2:0] 在 BUSY 下降沿锁存，无过采样时 tCONV=1.74µs，64倍过采样时 tCONV=167µs
- 读取时序：168MHz 下 GPIO 写操作 + 5个 NOP (~30ns) 覆盖 t10=22ns 和 t14=21ns 要求
- 硬件 RANGE 接 GND → ±5V 量程，LSB=152.59µV

## NET 网络层关键点

- **文件结构**：`Drivers/BSP/NET/` 下包含 net_config.h、net_types.h、net_socket.h/c、net_select.h/c
- **使用前必须调用** `net_init(ip, mask, gateway)` 初始化网络子系统
- **`net_poll()` 在 FreeRTOS netTask 中定期调用**（10ms 周期），轮询所有 Socket 状态，处理中断事件
- **TCP Server 多连接模式**：1 个监听 Socket + 最多 7 个数据 Socket
- **select/poll 支持**：`net_select()` 和 `net_poll_events()` 用于 I/O 多路复用
- **事件回调**：可通过 `net_set_event_cb()` 注册连接/断开/数据到达等事件回调
- **非阻塞模式**：`net_recv()` 使用 `NET_MSG_DONTWAIT` 标志，或使用 select/poll
- **字节序转换**：使用 `net_htons()/net_ntohs()/net_htonl()/net_ntohl()` 进行主机序/网络序转换
- **IP 地址转换**：`net_inet_addr("192.168.1.100")` 字符串转网络序，`net_inet_ntoa()` 反向转换
- **注意**：新文件需手动添加到 Keil MDK 工程中才能编译
- **RECV 中断是电平触发的**：`net_poll()` 中 `do { ... } while(0)` 只处理一批中断，由主循环读取数据，避免无限循环导致主循环饿死
- **TCP Server 多连接模式**：Socket 0 专职监听，Socket 1~7 自动分配
- **多连接模式初始化顺序（关键！遗漏将导致 TCP 连接失败）**：
  1. `ch395f_set_fun_para(0x02)` — 启用多连接（在 `INIT_CH395` 之前）
  2. `ch395f_init()` — 协议栈初始化
  3. **先配置数据 Socket 1~7**（必须！必须在监听 Socket 之前配置，CH395F 在 `TCP_LISTEN` 时刻扫描可用 Socket）：
     - `SET_SEND_BUF` → `SET_RECV_BUF` → `SET_SOUR_PORT（与监听端口相同）` → `SET_PROTO_TCP`
     - 数据 Socket 不调用 `OPEN_SOCKET`，由 CH395F 在连接到达时自动打开
  4. **再配置监听 Socket 0**：
     - `SET_PROTO_TCP → SET_SOUR_PORT → OPEN → TCP_LISTEN`
  - 如果不按此顺序，Socket 4~7 不可用，最多只接受 3 个并发连接
- **TCP Server 单连接模式**：Socket 先 `OPEN` → `SET_PROTO_TCP` → `SET_SOUR_PORT` → `OPEN` → `TCP_LISTEN`；收到 `SINT_STAT_CONNECT` 后 Socket 自动切换为数据通道；`DISCONNECT`/`TIMEOUT` 后需重新 `OPEN` → `TCP_LISTEN`

## 测试状态

| Phase | 描述 | 状态 | 备注 |
|-------|------|------|------|
| 1 | 寄存器测试 | ✅ 19/19 | |
| 2 | TCP Client | ✅ 3/3 | |
| 3 | UDP Server | ✅ 通过 | |
| 4 | NET TCP Echo | ✅ 通过 | netTask 中永久运行 |
| 5 | NET UDP Echo | ✅ 通过 | |
| 6 | DHCP | ✅ 通过 | 支持广播宣告 + DHCP Server 两种模式 |
| 7 | Select/Poll | ✅ 通过 | |
| 8 | 多客户端 7 并发 | ✅ 70/70 100% | 修复顺序后通过 |

## 陷阱记录

详细陷阱记录（含根因分析、解决方案、发现时间）见 `docs/CH395F_Trap_Records.md`。

当前已记录陷阱：
- **Trap 01** Socket 4~7 自动分配不到（初始化顺序）
- **Trap 02** 数据 Socket 缓冲区重叠
- **Trap 03** DHCP 包 xid 偏移错误
- **Trap 04** UDP 发送未等待 SENDBUF_FREE
- **Trap 05** CH395F 与 RTL8305NBI 自动协商不兼容
- **Trap 06** TCP KeepAlive 参数非 500ms 倍数
- **Trap 07** TCP 关闭误用 disconnect 导致 FIN_WAIT_2
- **Trap 08** RECV 中断电平触发无限循环
