Files
STM32F4-Base/tools/arch_diagram/gen_barrier_svg.py
T
wandering1 a6cdebde62 文档体系重组与 D-7 一致性门禁落地,清理芯片手册孤儿图片
- D-6 目录规范化:docs 根目录散落文档归入 UI设计/子系统设计/架构设计/审查记录/
  环境与部署/网络 等子目录,去掉目录名空格,重命名 嵌入式C语言代码规范_V1.0.md
- D-7 三档一致性门禁:新增 scripts/check_doc_links.py(链接存在性)、
  scripts/gen_task_table.py(FreeRTOS 任务表数值)、scripts/check_mem_numbers.py
  (内存/Flash 体积),均支持 --check/--write/--diff,pre-commit 钩子同步注册
- D-5 文档索引:新建 docs/README.md,新建 docs/文档规范.md 约束目录命名与链接写法
- D-1 芯片手册瘦身:删除 1069 张无引用孤儿图片(77.06 MB),
  docs/芯片手册 120 MB → 41 MB,已引用图片零断链
- 同步 C 源码注释中的文档路径引用(D-6 重组后),构建 0 错误 0 警告
2026-09-26 00:51:53 +08:00

247 lines
10 KiB
Python
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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 docs/images/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。
本脚本手工排布为三栏:
左 = 生产者(set) 中 = 事件组 sys_evtHandle 右 = 消费者(wait)
任务卡与事件位各自对齐到同一水平行,箭头基本水平/正交;bit1 → ftpTask 的
"与"依赖用汇合点(All)表达,避免跨行连线。
用法(仓库根目录,任意 python3):
python tools/arch_diagram/gen_barrier_svg.py
输出:
docs/images/就绪屏障.svg
改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。
"""
import html
import math
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 = 1400
PAD = 40
TITLE_FONT = 20
SUB_FONT = 13
CARD_TITLE_FONT = 15
CARD_NOTE_FONT = 12
BIT_TITLE_FONT = 14
BIT_NOTE_FONT = 12
LABEL_FONT = 11.5
FOOT_FONT = 12
H_CARD = 84 # 任务卡 / 事件位行高
GAP = 26 # 行间距
MAIN_Y = 200 # 首行 y
CARD_W_L = 290 # 生产者卡宽
CARD_W_R = 450 # 消费者卡宽
PANEL_X, PANEL_W = 400, 420
PROD_X = 40
CONS_X = 910
TAP_X = 832 # bit1 → All 汇合点的下引线 x
BUS_X = 862 # bit0 分发总线 x
ARROW_W = 2.2
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
C_PROD, BG_PROD = "#2563eb", "#eff6ff"
C_CONS, BG_CONS = "#7c3aed", "#f5f3ff"
# ----------------------------- 数据(改这里) -----------------------------
# 事件位(中间栏),自上而下 = 时序先后
BITS = [
dict(bit="bit1", flag="STORAGE_READY", color="#16a34a", bg="#f0fdf4",
note="defaultTask 置位(挂载+配置)· netTask / ftpTask 等待"),
dict(bit="bit0", flag="NET_READY", color="#0d9488", bg="#f0fdfa",
note="netTask 置位 · 4 个任务等待"),
dict(bit="bit2", flag="FTP_READY", color="#d97706", bg="#fffbeb",
note="ftpTask 置位 · 预留消费者"),
dict(bit="bit3", flag="TIMESYNC_READY", color="#94a3b8", bg="#f8fafc",
note="预留(尚未接入调度)"),
]
# 生产者(左栏):row = 其置位的 bit 行
PRODUCERS = [
dict(name="defaultTask", note="卷挂载 + net_cfg_load(f_mount)", row=0, label="set bit1"),
dict(name="netTask", note="net_stack_bring_up()", row=1, label="set bit0"),
dict(name="ftpTask", note='lftpd_start("/", 21)', row=2, label="set bit2"),
]
# 消费者(右栏):row = 卡片排布行(前两行与 bit1 / bit0 对齐)
CONSUMERS = [
dict(name="netTask", note="wait bit1 · ≤10s", row=0),
dict(name="ftpTask", note="wait bit0 + bit1 · All", row=1),
dict(name="uiTask", note="wait bit0 · Forever", row=2),
dict(name="timeSyncTask", note="wait bit0 · Forever", row=3),
dict(name="testTask", note="wait bit0 · NET 模式", row=4),
]
FOOT = ("生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set,"
"不改已有任务。详见 docs/架构设计/项目启动顺序管理.md")
# ----------------------------- 工具函数 -----------------------------
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) >= 0x2E80 else 0.58)
return w
# ----------------------------- 生成 -----------------------------
def build():
out = []
add = out.append
def row_y(r):
return MAIN_Y + r * (H_CARD + GAP)
def row_cy(r):
return row_y(r) + H_CARD / 2.0
def poly(points, color, dash=False, head=True, width=ARROW_W):
pts = " ".join("%.1f,%.1f" % p for p in points)
d = ' stroke-dasharray="7 5"' if dash else ''
add('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f" '
'stroke-linejoin="round"%s/>' % (pts, color, width, d))
if head:
(x1, y1), (x2, y2) = points[-2], points[-1]
ang = math.atan2(y2 - y1, x2 - x1)
hl, hw = 11.0, 5.5
bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang)
px, py = -math.sin(ang) * hw, math.cos(ang) * hw
add('<path d="M %.1f %.1f L %.1f %.1f L %.1f %.1f z" fill="%s"/>'
% (x2, y2, bx + px, by + py, bx - px, by - py, color))
def card(x, y, w, title, note, color, bg):
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" '
'stroke="%s" stroke-opacity="0.5"/>' % (x, y, w, H_CARD, bg, color))
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
% (x + 16, y + 34, CARD_TITLE_FONT, esc(title)))
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
% (x + 16, y + 58, CARD_NOTE_FONT, esc(note)))
# 行/画布尺寸
n_rows = max(len(CONSUMERS), len(BITS))
main_bottom = row_y(n_rows - 1) + H_CARD
total_h = main_bottom + 78
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
# ---- 标题 ----
add('<text x="%d" y="42" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
% (PAD, TITLE_FONT, esc("就绪屏障(Readiness Barrier)· 启动依赖链")))
add('<text x="%d" y="68" font-size="%d" fill="#64748b">%s</text>'
% (PAD, SUB_FONT,
esc("CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待")))
# ---- 图例 ----
lx, ly = PAD, 108
for kind, color, label in [
("rect", C_PROD, "生产者(set)"),
("rect", C_CONS, "消费者(wait)"),
("rect", "#16a34a", "事件位(Event Flag)"),
("dot", "#0d9488", "All 汇合(等全部位)"),
("dash", "#94a3b8", "预留 / 未接入"),
]:
if kind == "rect":
add('<rect x="%d" y="%d" width="16" height="16" rx="4" fill="#ffffff" '
'stroke="%s" stroke-width="2"/>' % (lx, ly - 12, color))
elif kind == "dot":
add('<circle cx="%d" cy="%d" r="6" fill="%s"/>' % (lx + 8, ly - 4, color))
else:
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2.4" '
'stroke-dasharray="7 5"/>' % (lx, ly - 4, lx + 16, ly - 4, color))
lx += 24
add('<text x="%d" y="%d" font-size="12" fill="#475569">%s</text>' % (lx, ly, esc(label)))
lx += text_w(label, 12) + 34
# ---- 事件组面板 ----
py0 = MAIN_Y - 34
py1 = row_y(len(BITS) - 1) + H_CARD + 14
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
'stroke="#64748b" stroke-opacity="0.35"/>' % (PANEL_X, py0, PANEL_W, py1 - py0))
head = "sys_evtHandle(Event Flags 事件组)"
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#334155">%s</text>'
% (PANEL_X + 16, py0 + 26, esc(head)))
for i, b in enumerate(BITS):
by = row_y(i)
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" stroke="%s" '
'stroke-opacity="0.6"/>' % (PANEL_X + 14, by, PANEL_W - 28, H_CARD, b["bg"], b["color"]))
add('<rect x="%d" y="%d" width="30" height="30" rx="7" fill="%s"/>'
% (PANEL_X + 28, by + (H_CARD - 30) / 2, b["color"]))
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#ffffff" '
'text-anchor="middle">%s</text>'
% (PANEL_X + 43, by + H_CARD / 2 + 5, esc(b["bit"][3])))
tx = PANEL_X + 70
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
% (tx, by + 36, BIT_TITLE_FONT, b["color"], esc(b["bit"] + " · " + b["flag"])))
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
% (tx, by + 58, BIT_NOTE_FONT, esc(b["note"])))
# ---- 生产者卡 + set 箭头 ----
for p in PRODUCERS:
y, cy = row_y(p["row"]), row_cy(p["row"])
card(PROD_X, y, CARD_W_L, p["name"], p["note"], C_PROD, BG_PROD)
poly([(PROD_X + CARD_W_L, cy), (PANEL_X, cy)], BITS[p["row"]]["color"])
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
% ((PROD_X + CARD_W_L + PANEL_X) / 2.0, cy - 8, LABEL_FONT,
BITS[p["row"]]["color"], esc(p["label"])))
# ---- 消费者卡 ----
for c in CONSUMERS:
card(CONS_X, row_y(c["row"]), CARD_W_R, c["name"], c["note"], C_CONS, BG_CONS)
# ---- wait 连线:bit1 → netTask(水平) ----
c0, c1 = row_cy(0), row_cy(1)
col1, col0 = BITS[0]["color"], BITS[1]["color"]
poly([(PANEL_X + PANEL_W, c0), (CONS_X, c0)], col1)
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
% ((PANEL_X + PANEL_W + CONS_X) / 2.0, c0 - 8, LABEL_FONT, col1, esc("wait")))
# ---- wait 连线:bit1 → All 汇合点(下引,与 bit0 相与) ----
poly([(TAP_X, c0), (TAP_X, c1)], col1, head=False)
add('<circle cx="%d" cy="%.1f" r="5.5" fill="%s"/>' % (TAP_X, c1, col0))
add('<text x="%d" y="%.1f" font-size="%s" font-weight="700" fill="%s">All</text>'
% (TAP_X + 9, c1 - 8, LABEL_FONT, col0))
# ---- wait 连线:bit0 → 总线 → 4 个消费者 ----
c_last = row_cy(len(CONSUMERS) - 1)
poly([(PANEL_X + PANEL_W, c1), (BUS_X, c1)], col0)
poly([(BUS_X, c1), (BUS_X, c_last)], col0, head=False)
for c in CONSUMERS[1:]:
poly([(BUS_X, row_cy(c["row"])), (CONS_X, row_cy(c["row"]))], col0)
mid_y = (c1 + c_last) / 2.0
add('<text transform="rotate(-90 %.1f %.1f)" x="%.1f" y="%.1f" font-size="%s" '
'fill="%s" text-anchor="middle">%s</text>'
% (BUS_X - 12, mid_y, BUS_X - 12, mid_y, LABEL_FONT, col0, esc("wait bit0")))
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
% (PAD, total_h - 18, 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, 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()