Files
STM32F4-Base/AGENTS.md

222 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STM32F4-Base
## 项目概述
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 CMSISCubeMX 生成)
├── 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 并行接口驱动 |
| `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`
- C 标准C99
- 全局宏定义:`USE_HAL_DRIVER, STM32F407xx`
### 命令行编译Agent 使用)
修改代码后必须执行命令行编译验证,编译脚本位于 `MDK-ARM/build.bat`
```
cmd /c "cd /d "工作目录" && build.bat"
```
实际执行示例(从项目根目录):
```
cmd /c "cd /d "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM" && build.bat"
```
**编译结果判读:**
- 退出码 0无错误无警告编译成功
- 退出码 1有警告无错误编译失败警告不可接受需修复
- 退出码 2+:编译失败,需查看 `MDK-ARM/build_log.txt` 定位错误
**UV4.exe 路径:** `C:\Keil_v5\UV4\UV4.exe`
**工程文件:** `MDK-ARM/STM32F407-Demo.uvprojx`
**目标名称:** `STM32F407-Demo`
## 硬件配置
- **主频:** 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()
```
## 代码规范
参考 `嵌入式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 保护
## CH395F 驱动关键点
- 初始化必须按手册9.2.1节顺序:`SET_MAC``SET_IP/GWIP/MASK``INIT_CH395``SET_PHY`
- **IP/网关/掩码必须在 `INIT_CH395` 之前设置**INIT 会读取并锁定当前寄存器值到协议栈,之后再设 IP 无效
- **`SET_PHY` 必须在 `INIT_CH395` 之后**,它会复位 MAC/PHY 建立物理链路,不影响已锁定的协议栈参数
- **`CMD_PING_ENABLE` 不需要显式调用**INIT 后默认可用
- 每次 SPI 事务需调用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹
- **大数据量收发使用 SPI2 DMA**`ch395f_write_send_buf()``ch395f_read_recv_buf()` 已改造为 DMA 批量传输DMA1_Stream3 RX, DMA1_Stream4 TX命令和配置操作仍使用逐字节轮询
- DMA 缓冲区:`s_spi2_dma_tx_buf[1500]` / `s_spi2_dma_rx_buf[1500]`4 字节对齐
- **TCP 重传参数**`SET_RETRAN_COUNT`默认12次最大20`SET_RETRAN_PERIOD`默认500ms最大1000ms必须在 `INIT_CH395` 之前设置,总重传时间 = 次数 × 周期
- **KeepAlive 参数必须为 500ms 的倍数**`SET_KEEPALIVE_IDLE`默认20000ms`SET_KEEPALIVE_INTVL`默认15000ms单位为 ms且 IDLE 必须 > INTVL均为 500 的倍数;传入非 500 倍数的值会导致未定义行为CH395F 内部定时器异常TCP 连接几秒内 TIMEOUT
- **KeepAlive 默认关闭**:需在 `SINT_STAT_CONNECT` 后调用 `ch395f_set_keepalive_enable(sock, 1)` 启用
- **Socket 4-7 默认无收发缓冲**CH395F 默认只为 Socket 0-3 各分配 4 个缓冲区块2048 字节)。多连接模式下使用 Socket 4-7 时,**必须在 `open_socket` 之前**显式分配:
- `ch395f_set_send_buf(sock, 28, 2)` — 分配发送缓冲(例:块 28-29共 1024 字节)
- `ch395f_set_recv_buf(sock, 30, 2)` — 分配接收缓冲(例:块 30-31共 1024 字节)
- 不分配会导致发送数据为固定垃圾内容(`0x0028` 填充)、接收缓冲区无法存储数据
- **TCP 关闭重连的正确方法****直接调用 `ch395f_close_socket()`,不要先调 `ch395f_tcp_disconnect()`**
-`disconnect``close``disconnect` 发送 FIN 将 Socket 推入 FIN_WAIT_2此后 `close` 不再发送 RSTSocket 卡在 FIN_WAIT_2 必须等远端发 FIN 才能关闭(可能需数分钟超时)
-`close` 直接:在 ESTABLISHED 或 CLOSE_WAIT 状态下调用 `close` 会发送 RST 立即终止连接Socket 瞬间回到 CLOSED已验证 `closed: sock=0x00 tcp=0x00`
- 关闭后需轮询 `ch395f_get_socket_status()` 等待 `sock=0x00``open_socket`,典型耗时 < 1s
- **SOCK_TIMEOUT 中断**:长时间无数据时 CH395F 可能触发 `SINT_STAT_SOCK_TIMEOUT`,不应将其视为致命错误——记录日志后继续操作即可,不要因此关闭 Socket
- **UDP 客户端/服务器模式**(手册 §9.2.4):通过 `SET_DES_IP_SN` 的 IP 地址区分:
- **DesIP=0xFFFFFFFF255.255.255.255)→ UDP Server 模式**:接受任意来源的数据,接收缓冲区中数据前 8 字节为信息头(`[0-1]reserved [2-3]src_port(LE) [4-7]src_ip [8+]payload`),回发前需设置 `SET_DES_IP``SET_DES_PORT` 指定目标
- **DesIP=具体 IP → UDP Client 模式**:只接受指定 IP:Port 的数据,接收数据无信息头,只能向预设的目标发送
- **UDP 发送缓冲必须等 SENDBUF_FREE**(手册要求):每次 `ch395f_write_send_buf()` 后必须等待 `SINT_STAT_SENBUF_FREE` 中断0x01否则下次写入会被 CH395F 静默丢弃。可通过轮询 `ch395f_get_sock_int_status()` 检查此标志
## 测试说明
- **测试代码文件**`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✅ 通过,主循环永久运行
- **Phase 5**NET 层 UDP Echo✅ 通过
- **Phase 6**DHCP 自动获取 IP✅ 通过
- **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 0CPOL=0, CPHA=0时钟 42MHz
- 初始化必须按顺序:复位 → 读 ID → 使能 ECCB0h=10h→ 解除块保护A0h=00h
- SET_FEATURE 命令前必须先发写使能06h
- 块擦除D8h参数是字节地址块编号 × 128KB不是块编号
- 读取 ID9Fh返回 3 字节,第 0 字节无意义,第 1 字节 MID第 2 字节 DID
## FlashDB 分区规划
| 分区名 | 偏移 | 大小 | 用途 |
|--------|------|------|------|
| fdb_kvdb1 | 0 | 64MB | KVDB 键值数据库 |
| fdb_tsdb1 | 64MB | 64MB | TSDB 时序数据库 |
## 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µs64倍过采样时 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()` 轮询所有 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 0: `SET_PROTO_TCP → SET_SOUR_PORT → OPEN → TCP_LISTEN`
4. **Socket 1~7: `SET_PROTO_TCP → SET_SOUR_PORT相同端口`(不 OPEN**
- 如果不配置数据 Socket 的协议类型和源端口CH395F 不知道哪些 Socket 可用于多连接分配,客户端连接会被静默拒绝(无 CONNECT 中断)
- **TCP Server 单连接模式**Socket 先 `OPEN``SET_PROTO_TCP``SET_SOUR_PORT``OPEN``TCP_LISTEN`;收到 `SINT_STAT_CONNECT` 后 Socket 自动切换为数据通道;`DISCONNECT`/`TIMEOUT` 后需重新 `OPEN``TCP_LISTEN`
## 已知问题
### CH395F 与 RTL8305NBI 自动协商不兼容
CH395F 与 RTL8305NBI-CG 直连(经网络变压器)时,自动协商始终失败(返回 `PHY_DISCONN`),但强制 100M 全双工工作正常。强制 10M 全双工同样失败。
**解决方案:** 初始化协议栈后调用 `ch395f_set_phy(CH395F_PHY_100M_FULL)` 跳过自动协商。