清理存量 ASCII 框图:存储栈/分区转 SVG、CH395F 时序转 Mermaid
This commit is contained in:
@@ -52,14 +52,6 @@ RemoDispBus 让远端(PC 上的 DTU-RemoteLCD 客户端 / 浏览器页面)
|
||||
| 4 | `DATA` | `LEN` | 数据段(命令相关,见 §1.5) |
|
||||
| 4+LEN | `CRC` | 1 | 数据段逐字节异或(低 8 位) |
|
||||
|
||||
```
|
||||
┌───────┬───────┬─────────┬─────────┬───────────┬───────┐
|
||||
│ TAG │ CMD │ LEN_H │ LEN_L │ DATA │ CRC │
|
||||
├───────┼───────┼─────────┼─────────┼───────────┼───────┤
|
||||
│ 1 B │ 1 B │ 1 B │ 1 B │ LEN B │ 1 B │
|
||||
└───────┴───────┴─────────┴─────────┴───────────┴───────┘
|
||||
```
|
||||
|
||||
- 整帧长度 = `4 + LEN + 1`;`LEN=0` 时最短为 5 字节。
|
||||
- `LEN` 为 16 位大端,理论最大 65535 数据字节。
|
||||
|
||||
|
||||
@@ -4,38 +4,13 @@
|
||||
|
||||
本 项目在 GD5F2GQ5UE SPI NAND Flash(256MB)上实现了三层存储软件栈:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 应用程序 (main, freertos) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ FatFs (文件系统) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ dhara FTL │
|
||||
│ (地址映射 · 磨损均衡 · 坏块管理 · 垃圾回收) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ GD5F2GQ5UE NAND Flash 驱动 (底层 SPI) │
|
||||
│ 硬件 SPI · 页读写 · 块擦除 · BBT · 内部 ECC 使能 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ SPI1 硬件外设 (PB3 SCK, PB4 MISO, PB5 MOSI) │
|
||||
│ 42MHz · Mode 0 · MSB First · DMA2 (S0-RX, S3-TX) │
|
||||
│ 小包轮询(≤32B) · 页数据 DMA (>32B) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||

|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 物理分区布局 (256MB) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ ftl_fatfs (256MB) │ │
|
||||
│ │ Block 0~2047 │ │
|
||||
│ │ dhara FTL + FatFS(全部 2048 个块) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ 偏移: 0 256MB│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
> 上图由 `tools/arch_diagram/gen_storage_stack_svg.py` 生成(手绘 SVG,布局确定、层间单向箭头);改分层后编辑脚本顶部 `L_*` / `ROWS` 数据重跑即可。
|
||||
|
||||

|
||||
|
||||
> 上图由 `tools/arch_diagram/gen_partition_svg.py` 生成(分区宽 ∝ MB 数 + 偏移标尺);改分区后编辑脚本顶部 `FIELDS` 数据重跑即可。
|
||||
|
||||
**数据路径:**
|
||||
- **应用** → `FatFS` → `dhara FTL` → `gd5f2gq5ue 驱动`。FTL 提供 LBA 到物理页的映射、磨损均衡、坏块管理和垃圾回收,FatFS 通过标准 `disk_*` 接口访问 FTL 提供的块设备。
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="528" viewBox="0 0 1000 528" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1000" height="528" fill="#ffffff"/>
|
||||
<text x="26" y="40" font-size="20" font-weight="700" fill="#0f172a">STM32F4-Base 存储软件栈</text>
|
||||
<text x="26" y="62" font-size="13" fill="#64748b">GD5F2GQ5UE SPI NAND Flash(256MB)· 三层软件栈(FatFs / dhara FTL / 驱动)· 上层只依赖相邻下层</text>
|
||||
<rect x="26" y="78" width="948" height="44" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.45"/>
|
||||
<text x="44" y="104" font-size="17" font-weight="700" fill="#2563eb">应用程序</text>
|
||||
<text x="124" y="104" font-size="12" fill="#64748b">main · freertos(CubeMX 生成 + FreeRTOS 任务)</text>
|
||||
<rect x="26" y="166" width="948" height="44" rx="12" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.45"/>
|
||||
<text x="44" y="192" font-size="17" font-weight="700" fill="#0d9488">FatFs</text>
|
||||
<text x="105" y="192" font-size="12" fill="#64748b">文件系统(标准 disk_* 块设备接口)</text>
|
||||
<rect x="26" y="254" width="948" height="44" rx="12" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
|
||||
<text x="44" y="280" font-size="17" font-weight="700" fill="#16a34a">dhara FTL</text>
|
||||
<text x="145" y="280" font-size="12" fill="#64748b">地址映射 · 磨损均衡 · 坏块管理 · 垃圾回收</text>
|
||||
<rect x="26" y="342" width="948" height="44" rx="12" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.45"/>
|
||||
<text x="44" y="368" font-size="17" font-weight="700" fill="#d97706">GD5F2GQ5UE NAND Flash 驱动</text>
|
||||
<text x="307" y="368" font-size="12" fill="#64748b">硬件 SPI · 页读写 · 块擦除 · BBT · 内部 ECC 使能</text>
|
||||
<rect x="26" y="430" width="948" height="44" rx="12" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
|
||||
<text x="44" y="456" font-size="17" font-weight="700" fill="#e11d48">SPI1 硬件外设</text>
|
||||
<text x="173" y="456" font-size="12" fill="#64748b">PB3 SCK · PB4 MISO · PB5 MOSI · 42MHz · Mode 0 · MSB First · DMA2(S0-RX / S3-TX)· 小包轮询(≤32B)/ 页数据 DMA(>32B)</text>
|
||||
<line x1="500.0" y1="128.0" x2="500.0" y2="151.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 500.0 158.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="500.0" y1="216.0" x2="500.0" y2="239.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 500.0 246.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="500.0" y1="304.0" x2="500.0" y2="327.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 500.0 334.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="500.0" y1="392.0" x2="500.0" y2="415.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 500.0 422.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<text x="26" y="510" font-size="12" fill="#94a3b8">数据路径:应用 → FatFs → dhara FTL → gd5f2gq5ue 驱动 → SPI1 硬件外设(单向依赖)· 详见 docs/STM32F4-Base存储架构说明.md §1</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1104" height="246" viewBox="0 0 1104 246" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1104" height="246" fill="#ffffff"/>
|
||||
<text x="40" y="40" font-size="20" font-weight="700" fill="#0f172a">GD5F2GQ5UE 物理分区布局(256MB)</text>
|
||||
<text x="40" y="64" font-size="13" fill="#64748b">单分区:全片划给 FTL(无静态分区)· 块 = 128KB(64 页 × 2KB)· 共 2048 块</text>
|
||||
<line x1="40" y1="96" x2="1064" y2="96" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<line x1="40" y1="92" x2="40" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="40" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">0</text>
|
||||
<line x1="1064" y1="92" x2="1064" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="1064" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">256 MB</text>
|
||||
<rect x="40" y="116" width="1024" height="74" fill="#eff6ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="552.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">ftl_fatfs</text>
|
||||
<text x="552.0" y="170" font-size="12" fill="#2563eb" text-anchor="middle">256 MB · Block 0~2047</text>
|
||||
<text x="40" y="230" font-size="12" fill="#94a3b8">ftl_fatfs:dhara FTL + FatFS 占用全部 2048 块(偏移 0 ~ 256MB)· 详见 docs/STM32F4-Base存储架构说明.md §1</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
+5
-1
@@ -12,7 +12,8 @@
|
||||
| 图形类型 | 工具 | 说明 |
|
||||
|---|---|---|
|
||||
| 协议帧 / 字节布局(精确比例) | **SVG**(`gen_frame_svg.py`) | 字段宽须严格 ∝ 字节数 + 逐字段标注 + 偏移标尺 |
|
||||
| 分层架构 / 模块依赖 | **SVG**(`gen_arch_svg.py`) | 多节点跨层依赖,Mermaid 会画成"意大利面",手工排布更清晰 |
|
||||
| 闪存 / 内存分区布局(精确比例) | **SVG**(`gen_partition_svg.py`) | 帧图生成器的 MB 计量副本(分区宽 ∝ MB 数 + 偏移标尺) |
|
||||
| 分层架构 / 模块依赖 | **SVG**(`gen_arch_svg.py`) | 多节点跨层依赖,Mermaid 会画成"意大利面",手工排布更清晰;单层单职责的栈图用其副本 `gen_storage_stack_svg.py` |
|
||||
| 就绪屏障 / 生产者-消费者 | **SVG**(`gen_barrier_svg.py`) | 三栏对齐 + 正交连线,避免交叉 |
|
||||
| 流程图 / 时序 / 状态机 / ER | **Mermaid**(Markdown 内嵌) | 拓扑关系类图,节点少、强调连接;渲染器原生支持 |
|
||||
| UI 界面 / 像素级样机 | `simulator/` 截图、`tools/ui_mock/` | 非本流程范围 |
|
||||
@@ -41,6 +42,8 @@
|
||||
| `gen_arch_svg.py` | `docs/images/项目架构总览.svg` | `LAYERS` / `ROWS` / `FOOT` |
|
||||
| `gen_barrier_svg.py` | `docs/images/就绪屏障.svg` | `BITS` / `PRODUCERS` / `CONSUMERS` |
|
||||
| `gen_frame_svg.py` | `docs/images/RemoDispBus帧格式.svg` | `FIELDS` / `TITLE` / `SUB` / `FOOT` |
|
||||
| `gen_storage_stack_svg.py` | `docs/images/存储栈分层.svg` | `L_*` / `ROWS` / `TITLE` / `SUB` / `FOOT` |
|
||||
| `gen_partition_svg.py` | `docs/images/物理分区布局.svg` | `FIELDS` / `TITLE` / `SUB` / `FOOT`(MB 计量) |
|
||||
|
||||
---
|
||||
|
||||
@@ -131,3 +134,4 @@ sequenceDiagram
|
||||
|---|---|
|
||||
| 2026-09-11 | 创建:统一图形绘制流程(选型 / SVG 生成器 / Mermaid / 约定 / 检查清单);新增 `tools/arch_diagram/gen_frame_svg.py`(通用协议帧图)+ `docs/RemoDispBus帧格式.svg` |
|
||||
| 2026-09-11 | 新增专门配图目录 `docs/images/`:收拢 `docs/` 根与 `docs/UI 设计/images/` 下 5 张 SVG;同步更新 3 个生成器 OUT 路径、4 处文档图片引用、`AGENTS.md` 与 `.gitignore`(PNG 预览规则);流程沉淀为 `draw-diagram` skill |
|
||||
| 2026-09-11 | 存量 ASCII 框图清理:`STM32F4-Base存储架构说明.md` 两图(存储栈分层 / 物理分区布局)重绘为 SVG(生成器家族新增 `gen_storage_stack_svg.py` / `gen_partition_svg.py`);`RemoDispBus协议与接收设计说明.md` 删 ASCII 帧图(SVG 帧格式图已在,信息无损失);`测试说明/CH395F_Test_Guide.md` 4 图(2 组件架构 + 2 会话时序)转 Mermaid |
|
||||
|
||||
@@ -522,46 +522,35 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081)
|
||||
|
||||
**架构图(组件)**:
|
||||
|
||||
```
|
||||
┌──────────────┐ TCP 8080 ┌──────────────────────────┐
|
||||
│ PC (Python) │ ───────────────────────────────────────────▶ │ CH395F (192.168.1.100) │
|
||||
│ tcp_client / │ Ethernet │ Socket 0(单连接复用) │
|
||||
│ tcp_server │ (SPI2, 分频=8) └────────────┬─────────────┘
|
||||
└──────────────┘ SPI │
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ MCU (STM32F407) │
|
||||
│ netTask: net_poll() 10ms │
|
||||
│ testTask: │
|
||||
│ net_layer_test_main() │
|
||||
│ └─ phase6_run() 原样回显 │
|
||||
└──────────────────────────┘
|
||||
```mermaid
|
||||
flowchart LR
|
||||
PC["PC(Python)<br/>tcp_client / tcp_server"] -->|"TCP 8080 · 以太网"| CH["CH395F(192.168.1.100)<br/>Socket 0(单连接复用)"]
|
||||
CH -->|"SPI2(分频=8)"| MCU["MCU(STM32F407)<br/>netTask: net_poll() 10ms<br/>testTask: net_layer_test_main()<br/>└─ phase6_run() 原样回显"]
|
||||
```
|
||||
|
||||
**会话时序图(单连接生命周期)**:
|
||||
|
||||
```
|
||||
PC (Client) CH395F Socket 0 MCU testTask
|
||||
│ │ │
|
||||
│ SYN ──────────────────▶│ │
|
||||
│◀──────────────── SYN+ACK │ │
|
||||
│ ACK ──────────────────▶│ ESTABLISHED │
|
||||
│ │── 中断/GlobInt ───────────▶│ net_poll()
|
||||
│ │ │ net_accept()→phase_active
|
||||
│ [64B 数据] ───────────▶│ recv_len = N │
|
||||
│ │── 中断 ───────────────────▶│ net_poll()
|
||||
│ │ │ phase6_run():
|
||||
│ │ │ net_recv(N) → 字节校验
|
||||
│ │ │ TEST_CHECK(累加和)
|
||||
│◀──────────── [64B 回显]─│ net_send() │
|
||||
│ (逐字节比对 + 累加和) │ │
|
||||
│ (重复 N 轮 / 多包) │ │
|
||||
│ │ │
|
||||
│ FIN/RST ───────────────▶│ DISCONNECT │
|
||||
│ │── 中断 ───────────────────▶│ net_poll()
|
||||
│ │ │ net_recv EOF → auto_relisten
|
||||
│ │◀─ 自动 OPEN + TCP_LISTEN │
|
||||
│ SYN(新连接) ───────────▶│ LISTEN 自动恢复,重新接受 │
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as PC(Client)
|
||||
participant S as CH395F Socket 0
|
||||
participant T as MCU testTask
|
||||
C->>S: SYN
|
||||
S->>C: SYN+ACK
|
||||
C->>S: ACK(ESTABLISHED)
|
||||
S->>T: 中断 / GlobInt → net_poll()
|
||||
Note over T: net_accept() → phase_active
|
||||
C->>S: [64B 数据](recv_len = N)
|
||||
S->>T: 中断 → net_poll()
|
||||
Note over T: phase6_run():net_recv(N) → 字节校验 → TEST_CHECK(累加和)
|
||||
S->>C: [64B 回显](net_send)
|
||||
Note over C: 逐字节比对 + 累加和(重复 N 轮 / 多包)
|
||||
C->>S: FIN/RST(DISCONNECT)
|
||||
S->>T: 中断 → net_poll()
|
||||
Note over T: net_recv EOF → auto_relisten
|
||||
S->>S: 自动 OPEN + TCP_LISTEN
|
||||
C->>S: SYN(新连接)
|
||||
Note over C,S: LISTEN 自动恢复,重新接受
|
||||
```
|
||||
|
||||
- `phase6_run()` 为**纯回显**:非阻塞 `net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT)` 收到 N 字节后,`net_send(sockfd, s_rx_buf, N, 0)` 原样回显;不做固件侧字节自检(字节完整性由 PC 侧 `data == test_data` 校验)。
|
||||
@@ -728,40 +717,32 @@ net_socket(NET_AF_INET, NET_SOCK_STREAM) // 取空闲 Socket(非监听 So
|
||||
|
||||
**架构图(组件)**:
|
||||
|
||||
```
|
||||
┌──────────────┐ TCP 8081 ┌──────────────────────────┐
|
||||
│ PC (Python) │ ◀─────────────────────────────────────────── │ CH395F (192.168.1.100) │
|
||||
│ tcp_server │ Ethernet │ Socket N(非 0 空闲) │
|
||||
│ (回显服务器) │ (SPI2, 分频=8) └────────────┬─────────────┘
|
||||
└──────────────┘ SPI │
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ MCU (STM32F407) │
|
||||
│ netTask: net_poll() 10ms │
|
||||
│ testTask: │
|
||||
│ net_layer_client_test_main│
|
||||
│ └─ net_connect→收发→close│
|
||||
└──────────────────────────┘
|
||||
```mermaid
|
||||
flowchart LR
|
||||
MCU["MCU(STM32F407)<br/>netTask: net_poll() 10ms<br/>testTask: net_layer_client_test_main()<br/>└─ net_connect → 收发 → close"] -->|"SPI2(分频=8)"| CH["CH395F(192.168.1.100)<br/>Socket N(非 0 空闲)"]
|
||||
CH -->|"TCP 8081 · 以太网"| PC["PC(Python)<br/>tcp_server(回显服务器)"]
|
||||
```
|
||||
|
||||
**会话时序图(Client 主动连接)**:
|
||||
|
||||
```
|
||||
PC (Server :8081) CH395F Socket N MCU testTask
|
||||
│ │ │
|
||||
│ (Python tcp_server) │ │ net_layer_client_test_main()
|
||||
│ │◀─ net_socket()+net_connect()│ (异步下发 CONNECT)
|
||||
│◀────────────── SYN ──────│ │
|
||||
│────── SYN+ACK ─────────▶│ ESTABLISHED │
|
||||
│ │── 中断/GlobInt ───────────▶│ net_poll()
|
||||
│ │ │ net_select() 可写 → 建链
|
||||
│◀────────────── [64B] ────│ net_send() │
|
||||
│ (回显服务器原样返回) │ │
|
||||
│ [64B 回显] ───────────▶│ recv_len = N │
|
||||
│ │── 中断 ───────────────────▶│ net_poll()
|
||||
│ │ │ net_recv(N) → 字节校验
|
||||
│ │ │ TEST_CHECK(累加和)
|
||||
│ │◀─ net_close() ─────────────│ 释放 Socket N
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as PC(Server :8081)
|
||||
participant S as CH395F Socket N
|
||||
participant T as MCU testTask
|
||||
Note over T: net_layer_client_test_main()
|
||||
T->>S: net_socket() + net_connect()(异步下发 CONNECT)
|
||||
S->>C: SYN
|
||||
C->>S: SYN+ACK(ESTABLISHED)
|
||||
S->>T: 中断 / GlobInt → net_poll()
|
||||
Note over T: net_select() 可写 → 建链
|
||||
T->>S: [64B](net_send)
|
||||
S->>C: [64B]
|
||||
Note over C: 回显服务器原样返回
|
||||
C->>S: [64B 回显](recv_len = N)
|
||||
S->>T: 中断 → net_poll()
|
||||
Note over T: net_recv(N) → 字节校验 → TEST_CHECK(累加和)
|
||||
T->>S: net_close()(释放 Socket N)
|
||||
```
|
||||
|
||||
- `net_layer_client_test_main()` 为**独立入口**:使用非 0 空闲 Socket 连接 PC,不在 `net_layer_test_main()` 的监听循环内;由 `StartCh395fTestTask` 的 NET 分支按宏调用。
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/images/物理分区布局.svg —— GD5F2GQ5UE 全片分区布局图(纯标准库)。
|
||||
|
||||
由 gen_frame_svg.py 复制而来(build() 与数据解耦,通用):协议帧图按"字节"计量
|
||||
(字段宽 ∝ 字节数),本图按"MB"计量(分区宽 ∝ MB 数),其余约定一致——
|
||||
分段矩形 + 偏移标尺 + 逐段标注,连续布局无假箭头。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_partition_svg.py
|
||||
输出:
|
||||
docs/images/物理分区布局.svg
|
||||
改分区时:编辑本文件顶部的 FIELDS / TITLE / SUB / FOOT 后重跑。
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "images", "物理分区布局.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
PAD = 40 # 左右留白
|
||||
PX_PER_MB = 4 # 每 MB 像素宽(分区宽 ∝ MB 数)
|
||||
BAR_Y = 116 # 分区盒顶 y(标题 / 副标题 / 偏移标尺在其上方)
|
||||
BAR_H = 74 # 分区盒高
|
||||
NAME_DY = 30 # 分区名在盒内的 y 偏移
|
||||
SIZE_DY = 54 # 尺寸标注在盒内的 y 偏移
|
||||
RULER_DY = 20 # 偏移标尺线在盒顶上方
|
||||
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
NAME_FONT = 15
|
||||
SIZE_FONT = 12
|
||||
RULER_FONT = 12
|
||||
FOOT_FONT = 12
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
# ----------------------------- 分区数据(改这里) -----------------------------
|
||||
# name=分区名 mb=MB 数(None=变长段,按 DATA_UNITS 画) label=尺寸标注 color/bg=配色
|
||||
DATA_UNITS = 5 # 变长段的"代表宽度"(按 N MB 画,仅示意)
|
||||
FIELDS = [
|
||||
dict(name="ftl_fatfs", mb=256, label="256 MB · Block 0~2047", color="#2563eb", bg="#eff6ff"),
|
||||
]
|
||||
|
||||
TITLE = "GD5F2GQ5UE 物理分区布局(256MB)"
|
||||
SUB = "单分区:全片划给 FTL(无静态分区)· 块 = 128KB(64 页 × 2KB)· 共 2048 块"
|
||||
FOOT = ("ftl_fatfs:dhara FTL + FatFS 占用全部 2048 块(偏移 0 ~ 256MB)· "
|
||||
"详见 docs/STM32F4-Base存储架构说明.md §1")
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def field_w(f):
|
||||
return (f["mb"] if f["mb"] is not None else DATA_UNITS) * PX_PER_MB
|
||||
|
||||
|
||||
def offset_labels(fields):
|
||||
"""按分区 MB 数累计边界偏移;偏移标注统一带 MB 单位。"""
|
||||
labs, off = ["0"], 0
|
||||
for f in fields:
|
||||
off += f["mb"] if f["mb"] is not None else DATA_UNITS
|
||||
labs.append("%d MB" % off)
|
||||
return labs
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
frame_w = sum(field_w(f) for f in FIELDS)
|
||||
W = frame_w + 2 * PAD
|
||||
|
||||
# 预计算各分区 x / 宽
|
||||
segs, x = [], PAD
|
||||
for f in FIELDS:
|
||||
w = field_w(f)
|
||||
segs.append([f, x, w])
|
||||
x += w
|
||||
labels = offset_labels(FIELDS)
|
||||
|
||||
total_h = BAR_Y + BAR_H + 56
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
|
||||
# 标题 + 副标题
|
||||
add('<text x="%d" y="40" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, TITLE_FONT, esc(TITLE)))
|
||||
add('<text x="%d" y="64" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, SUB_FONT, esc(SUB)))
|
||||
|
||||
# 偏移标尺(每个分区边界一个刻度 + 偏移标注)
|
||||
ruler_y = BAR_Y - RULER_DY
|
||||
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#cbd5e1" stroke-width="1"/>'
|
||||
% (PAD, ruler_y, PAD + frame_w, ruler_y))
|
||||
bounds = [PAD] + [s[1] + s[2] for s in segs]
|
||||
for i, bx in enumerate(bounds):
|
||||
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#94a3b8" stroke-width="1.5"/>'
|
||||
% (bx, ruler_y - 4, bx, ruler_y + 4))
|
||||
lab = labels[i] if i < len(labels) else "?"
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8" text-anchor="middle">%s</text>'
|
||||
% (bx, ruler_y - 8, RULER_FONT, esc(lab)))
|
||||
|
||||
# 分区盒(名在上、尺寸在下)
|
||||
for (f, bx, bw) in segs:
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" fill="%s" stroke="%s" stroke-width="1.5"/>'
|
||||
% (bx, BAR_Y, bw, BAR_H, f["bg"], f["color"]))
|
||||
cx = bx + bw / 2.0
|
||||
add('<text x="%.1f" y="%d" font-size="%d" font-weight="700" fill="#0f172a" '
|
||||
'text-anchor="middle">%s</text>'
|
||||
% (cx, BAR_Y + NAME_DY, NAME_FONT, esc(f["name"])))
|
||||
add('<text x="%.1f" y="%d" font-size="%d" fill="%s" text-anchor="middle">%s</text>'
|
||||
% (cx, BAR_Y + SIZE_DY, SIZE_FONT, f["color"], esc(f["label"])))
|
||||
|
||||
# 脚注
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 16, FOOT_FONT, esc(FOOT)))
|
||||
|
||||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
|
||||
% (W, total_h, W, total_h, FONT, "\n".join(out)))
|
||||
return svg, W, total_h
|
||||
|
||||
|
||||
def main():
|
||||
svg, W, h = build()
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(svg)
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(svg)
|
||||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/images/存储栈分层.svg —— GD5F2GQ5UE 存储软件栈分层图(纯标准库)。
|
||||
|
||||
由 gen_arch_svg.py 复制而来(build() 与数据解耦,通用):每层一个圆角框(标题 + 说明),
|
||||
层间单向下箭头。本图各层均为"单层单职责",故 chips 留空(只渲染标题 + 说明行)。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_storage_stack_svg.py
|
||||
输出:
|
||||
docs/images/存储栈分层.svg
|
||||
改分层时:编辑本文件顶部的 L_* / ROWS / TITLE / SUB / FOOT 数据后重跑。
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "images", "存储栈分层.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
W = 1000
|
||||
PAD = 26
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
BAND_TITLE_FONT = 17
|
||||
BAND_SUB_FONT = 12
|
||||
CHIP_FONT = 13
|
||||
|
||||
CHIP_H = 38 # 芯片盒高
|
||||
CHIP_GAP = 10 # 芯片水平间距
|
||||
CHIP_ROW_GAP = 10 # 芯片行间距
|
||||
CHIP_PAD = 16 # 芯片内左右留白
|
||||
BAND_PAD_X = 18 # 框内左右留白
|
||||
BAND_TITLE_H = 38 # 框内标题区高
|
||||
BAND_PAD_BOTTOM = 16
|
||||
BAND_H_GAP = 26 # 同一行内并列框的间距
|
||||
BAND_V_GAP = 44 # 行间距(放箭头)
|
||||
TITLE_H = 78 # 顶部标题区高
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
# ----------------------------- 架构数据(改这里) -----------------------------
|
||||
TITLE = "STM32F4-Base 存储软件栈"
|
||||
SUB = "GD5F2GQ5UE SPI NAND Flash(256MB)· 三层软件栈(FatFs / dhara FTL / 驱动)· 上层只依赖相邻下层"
|
||||
|
||||
L_APP = dict(
|
||||
title="应用程序", sub="main · freertos(CubeMX 生成 + FreeRTOS 任务)",
|
||||
color="#2563eb", bg="#eff6ff", chips=[],
|
||||
)
|
||||
L_FATFS = dict(
|
||||
title="FatFs", sub="文件系统(标准 disk_* 块设备接口)",
|
||||
color="#0d9488", bg="#f0fdfa", chips=[],
|
||||
)
|
||||
L_FTL = dict(
|
||||
title="dhara FTL", sub="地址映射 · 磨损均衡 · 坏块管理 · 垃圾回收",
|
||||
color="#16a34a", bg="#f0fdf4", chips=[],
|
||||
)
|
||||
L_DRV = dict(
|
||||
title="GD5F2GQ5UE NAND Flash 驱动", sub="硬件 SPI · 页读写 · 块擦除 · BBT · 内部 ECC 使能",
|
||||
color="#d97706", bg="#fffbeb", chips=[],
|
||||
)
|
||||
L_SPI = dict(
|
||||
title="SPI1 硬件外设",
|
||||
sub="PB3 SCK · PB4 MISO · PB5 MOSI · 42MHz · Mode 0 · MSB First · DMA2(S0-RX / S3-TX)· 小包轮询(≤32B)/ 页数据 DMA(>32B)",
|
||||
color="#e11d48", bg="#fff1f2", chips=[],
|
||||
)
|
||||
|
||||
ROWS = [
|
||||
[L_APP],
|
||||
[L_FATFS],
|
||||
[L_FTL],
|
||||
[L_DRV],
|
||||
[L_SPI],
|
||||
]
|
||||
|
||||
FOOT = "数据路径:应用 → FatFs → dhara FTL → gd5f2gq5ue 驱动 → SPI1 硬件外设(单向依赖)· 详见 docs/STM32F4-Base存储架构说明.md §1"
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def text_w(s, fs):
|
||||
"""粗略估算文本像素宽:CJK/全角/圈码/箭头 ≈ 1.0em,其余 ≈ 0.58em。
|
||||
|
||||
取偏大估计(宁可芯片略宽,不可文字溢出盒外)。
|
||||
"""
|
||||
w = 0.0
|
||||
for ch in s:
|
||||
w += fs * (1.0 if ord(ch) >= 0x2460 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
def chip_w(label):
|
||||
return text_w(label, CHIP_FONT) + 2 * CHIP_PAD
|
||||
|
||||
|
||||
def wrap_chips(chips, avail):
|
||||
rows, cur, cur_w = [], [], 0.0
|
||||
for c in chips:
|
||||
cw = chip_w(c)
|
||||
need = cw if not cur else cur_w + CHIP_GAP + cw
|
||||
if cur and need > avail:
|
||||
rows.append(cur)
|
||||
cur, cur_w = [c], cw
|
||||
else:
|
||||
cur.append(c)
|
||||
cur_w = need
|
||||
if cur:
|
||||
rows.append(cur)
|
||||
return rows
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
# 布局:逐行排布(行内可并列多个框,行高取该行最高框)
|
||||
placements = [] # (layer, chip_rows, x, y, w, h)
|
||||
y = TITLE_H
|
||||
for row in ROWS:
|
||||
n = len(row)
|
||||
bw = (W - 2 * PAD - BAND_H_GAP * (n - 1)) // n
|
||||
bands = []
|
||||
for i, layer in enumerate(row):
|
||||
bx = PAD + i * (bw + BAND_H_GAP)
|
||||
avail = bw - 2 * BAND_PAD_X
|
||||
rows = wrap_chips(layer["chips"], avail)
|
||||
bh = BAND_TITLE_H + len(rows) * CHIP_H + (len(rows) - 1) * CHIP_ROW_GAP + BAND_PAD_BOTTOM
|
||||
bands.append([layer, rows, bx, y, bw, bh])
|
||||
row_h = max(b[5] for b in bands)
|
||||
for b in bands:
|
||||
b[5] = row_h # 同行等高(视觉"同级")
|
||||
placements.extend(bands)
|
||||
y += row_h + BAND_V_GAP
|
||||
total_h = y - BAND_V_GAP + 54
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, 40, TITLE_FONT, esc(TITLE)))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, 62, SUB_FONT, esc(SUB)))
|
||||
|
||||
for (layer, rows, bx, by, bw, bh) in placements:
|
||||
# 框背景 + 边框
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="12" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.45"/>' % (bx, by, bw, bh, layer["bg"], layer["color"]))
|
||||
# 标题(层名 + 职责说明)
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (bx + BAND_PAD_X, by + 26, BAND_TITLE_FONT, layer["color"], esc(layer["title"])))
|
||||
tw = text_w(layer["title"], BAND_TITLE_FONT)
|
||||
add('<text x="%.0f" y="%d" font-size="%s" fill="#64748b">%s</text>'
|
||||
% (bx + BAND_PAD_X + tw + 12, by + 26, BAND_SUB_FONT, esc(layer["sub"])))
|
||||
# 芯片
|
||||
cy = by + BAND_TITLE_H
|
||||
for row in rows:
|
||||
cx = bx + BAND_PAD_X
|
||||
for c in row:
|
||||
cw = chip_w(c)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#ffffff" '
|
||||
'stroke="%s" stroke-opacity="0.55"/>' % (cx, cy, cw, CHIP_H, layer["color"]))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#1e293b" text-anchor="middle">%s</text>'
|
||||
% (cx + cw / 2, cy + CHIP_H / 2 + CHIP_FONT * 0.35, CHIP_FONT, esc(c)))
|
||||
cx += cw + CHIP_GAP
|
||||
cy += CHIP_H + CHIP_ROW_GAP
|
||||
|
||||
# 行间箭头:每个上层框向下引一条(顶层两框 → 两条,体现"都依赖下层")
|
||||
row_first = [] # 每行首个 placement 的下标
|
||||
idx = 0
|
||||
for row in ROWS:
|
||||
row_first.append(idx)
|
||||
idx += len(row)
|
||||
for r in range(len(ROWS) - 1):
|
||||
y0 = placements[row_first[r]][3] + placements[row_first[r]][5] + 6
|
||||
y1 = placements[row_first[r + 1]][3] - 8
|
||||
for i in range(row_first[r], row_first[r] + len(ROWS[r])):
|
||||
(_, _, bx, _, bw, _) = placements[i]
|
||||
ax = bx + bw / 2
|
||||
add('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#94a3b8" '
|
||||
'stroke-width="2.5"/>' % (ax, y0, ax, y1 - 7))
|
||||
add('<path d="M %.1f %.1f l -6 -9 h 12 z" fill="#94a3b8"/>' % (ax, y1))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, esc(FOOT)))
|
||||
|
||||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
|
||||
% (W, total_h, W, total_h, FONT, "\n".join(out)))
|
||||
return svg, total_h
|
||||
|
||||
|
||||
def main():
|
||||
svg, h = build()
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(svg)
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(svg)
|
||||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user