Files
DTU-HMI/docs/绘图/SPI_QSPI.py

208 lines
11 KiB
Python
Raw Permalink 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.
import xml.etree.ElementTree as ET
def make_qspi_mxfile():
# ========== 基础参数 ==========
y_cs_high, y_cs_low = 80, 110
y_sclk_high, y_sclk_low = 180, 210
y_io0_high, y_io0_low = 280, 310
y_io1_high, y_io1_low = 380, 410
# 时序参数50px/周期36个周期8命令+24地址+4数据
cycle_w = 50
start_x = 100
n_cycles = 36 # 8 + 24 + 4
end_x = start_x + n_cycles * cycle_w # 1900
tail_x = end_x + 50 # 1950
rise_edges = [start_x + i*cycle_w + cycle_w//2 for i in range(n_cycles)] # 125,175...
fall_edges = [start_x + (i+1)*cycle_w for i in range(n_cycles)] # 150,200...1900
# 数据定义
cmd_bits = [0,0,1,1,1,0,1,1] # 0x3B MSB first
addr_bytes = [
[0,0,0,1,0,0,1,0], # 0x12 A23-A16
[0,0,1,1,0,1,0,0], # 0x34 A15-A8
[0,1,0,1,0,1,1,0], # 0x56 A7-A0
]
addr_bits = [b for byte in addr_bytes for b in byte]
data_io0 = [0,0,0,0] # D0,D2,D4,D6 (示例 0xAA)
data_io1 = [1,1,1,1] # D1,D3,D5,D7
all_io0 = cmd_bits + addr_bits + data_io0
all_io1 = [0]*32 + data_io1 # 命令地址期间 IO1 idle/low
lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append('<mxfile host="app.diagrams.net" modified="2024-05-25T00:00:00.000Z" agent="AI" version="21.0.0" type="device">')
lines.append(' <diagram name="QSPI Waveform" id="qspi_waveform">')
lines.append(' <mxGraphModel dx="2000" dy="600" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2000" pageHeight="600" math="0" shadow="0">')
lines.append(' <root>')
lines.append(' <mxCell id="0"/>')
lines.append(' <mxCell id="1" parent="0"/>')
# 背景
lines.append(' <mxCell id="grid" value="" style="points=[];gridColor=#e0e0e0;gridSize=20;spacingTop=20;spacingLeft=20;spacingBottom=20;spacingRight=20;html=1;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="0" y="0" width="2100" height="600" as="geometry"/>')
lines.append(' </mxCell>')
# 标题
lines.append(' <mxCell id="title" value="QSPI Fast Read Dual Output (命令 0x3B)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;" vertex="1" parent="1">')
lines.append(' <mxGeometry x="600" y="20" width="500" height="30" as="geometry"/>')
lines.append(' </mxCell>')
# MSB / LSB
lines.append(' <mxCell id="msb" value="MSB" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontColor=#666666;fontStyle=1;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="{start_x+10}" y="60" width="40" height="20" as="geometry"/>')
lines.append(' </mxCell>')
lines.append(' <mxCell id="lsb" value="LSB" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontColor=#666666;fontStyle=1;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="{end_x-40}" y="60" width="40" height="20" as="geometry"/>')
lines.append(' </mxCell>')
# 信号标签
for label, y, eid in [("CS", 85, "label_cs"), ("SCLK", 185, "label_sclk"),
("IO0", 275, "label_io0"), ("IO1", 375, "label_io1")]:
lines.append(f' <mxCell id="{eid}" value="{label}" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="20" y="{y}" width="50" height="20" as="geometry"/>')
lines.append(' </mxCell>')
# 阶段分界虚线
boundaries = [
(start_x + 8*cycle_w, "cmd_addr"), # 500
(start_x + 16*cycle_w, "addr_mid1"), # 900
(start_x + 24*cycle_w, "addr_mid2"), # 1300
(start_x + 32*cycle_w, "addr_data"), # 1700
]
for x, eid in boundaries:
lines.append(f' <mxCell id="vl_{eid}" value="" style="endArrow=none;html=1;strokeWidth=1;strokeColor=#CCCCCC;dashed=1;" edge="1" parent="1">')
lines.append(' <mxGeometry width="50" height="50" relative="1" as="geometry">')
lines.append(f' <mxPoint x="{x}" y="180" as="sourcePoint"/>')
lines.append(f' <mxPoint x="{x}" y="420" as="targetPoint"/>')
lines.append(' </mxGeometry>')
lines.append(' </mxCell>')
# ========== CS 波形 ==========
cs_pts = [(80, y_cs_high), (90, y_cs_high), (90, y_cs_low), (tail_x, y_cs_low), (tail_x, y_cs_high), (tail_x+10, y_cs_high)]
lines.append(' <mxCell id="cs" value="" style="edgeStyle=none;html=1;strokeWidth=2;strokeColor=#FF5722;rounded=0;endArrow=none;" edge="1" parent="1">')
lines.append(' <mxGeometry relative="0" as="geometry">')
lines.append(f' <mxPoint x="{cs_pts[0][0]}" y="{cs_pts[0][1]}" as="sourcePoint"/>')
lines.append(f' <mxPoint x="{cs_pts[-1][0]}" y="{cs_pts[-1][1]}" as="targetPoint"/>')
lines.append(' <Array as="points">')
for x, y in cs_pts[1:-1]:
lines.append(f' <mxPoint x="{x}" y="{y}"/>')
lines.append(' </Array>')
lines.append(' </mxGeometry>')
lines.append(' </mxCell>')
# ========== SCLK 波形 ==========
sclk_pts = [(80, y_sclk_low), (start_x, y_sclk_low)]
for i in range(n_cycles):
rise = rise_edges[i]
fall = fall_edges[i]
sclk_pts.extend([
(rise, y_sclk_low),
(rise, y_sclk_high),
(fall, y_sclk_high),
(fall, y_sclk_low),
])
sclk_pts.append((tail_x, y_sclk_low))
lines.append(' <mxCell id="sclk" value="" style="edgeStyle=none;html=1;strokeWidth=2;strokeColor=#2196F3;rounded=0;endArrow=none;" edge="1" parent="1">')
lines.append(' <mxGeometry relative="0" as="geometry">')
lines.append(f' <mxPoint x="{sclk_pts[0][0]}" y="{sclk_pts[0][1]}" as="sourcePoint"/>')
lines.append(f' <mxPoint x="{sclk_pts[-1][0]}" y="{sclk_pts[-1][1]}" as="targetPoint"/>')
lines.append(' <Array as="points">')
for x, y in sclk_pts[1:-1]:
lines.append(f' <mxPoint x="{x}" y="{y}"/>')
lines.append(' </Array>')
lines.append(' </mxGeometry>')
lines.append(' </mxCell>')
# ========== IO0 波形 ==========
def build_wave_pts(bits, y_hi, y_lo, idle_y):
y0 = y_hi if bits[0] else y_lo
pts = [(80, y0), (start_x, y0)]
for i in range(len(bits)-1):
end_x = fall_edges[i]
y_curr = y_hi if bits[i] else y_lo
y_next = y_hi if bits[i+1] else y_lo
pts.append((end_x, y_curr))
if y_next != y_curr:
pts.append((end_x, y_next))
pts.append((end_x := fall_edges[len(bits)-1], y_hi if bits[-1] else y_lo))
pts.append((tail_x, pts[-1][1]))
return pts
io0_pts = build_wave_pts(all_io0, y_io0_high, y_io0_low, y_io0_low)
lines.append(' <mxCell id="io0" value="" style="edgeStyle=none;html=1;strokeWidth=2;strokeColor=#4CAF50;rounded=0;endArrow=none;" edge="1" parent="1">')
lines.append(' <mxGeometry relative="0" as="geometry">')
lines.append(f' <mxPoint x="{io0_pts[0][0]}" y="{io0_pts[0][1]}" as="sourcePoint"/>')
lines.append(f' <mxPoint x="{io0_pts[-1][0]}" y="{io0_pts[-1][1]}" as="targetPoint"/>')
lines.append(' <Array as="points">')
for x, y in io0_pts[1:-1]:
lines.append(f' <mxPoint x="{x}" y="{y}"/>')
lines.append(' </Array>')
lines.append(' </mxGeometry>')
lines.append(' </mxCell>')
# ========== IO1 波形 ==========
io1_pts = build_wave_pts(all_io1, y_io1_high, y_io1_low, y_io1_low)
lines.append(' <mxCell id="io1" value="" style="edgeStyle=none;html=1;strokeWidth=2;strokeColor=#9C27B0;rounded=0;endArrow=none;" edge="1" parent="1">')
lines.append(' <mxGeometry relative="0" as="geometry">')
lines.append(f' <mxPoint x="{io1_pts[0][0]}" y="{io1_pts[0][1]}" as="sourcePoint"/>')
lines.append(f' <mxPoint x="{io1_pts[-1][0]}" y="{io1_pts[-1][1]}" as="targetPoint"/>')
lines.append(' <Array as="points">')
for x, y in io1_pts[1:-1]:
lines.append(f' <mxPoint x="{x}" y="{y}"/>')
lines.append(' </Array>')
lines.append(' </mxGeometry>')
lines.append(' </mxCell>')
# ========== 命令 bit 标签IO0 上方) ==========
cmd_labels = ["0","0","1","1","1","0","1","1"]
for i, val in enumerate(cmd_labels):
cx = rise_edges[i]
lines.append(f' <mxCell id="cmd{i}" value="{val}" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontColor=#4CAF50;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="{cx-10}" y="255" width="20" height="20" as="geometry"/>')
lines.append(' </mxCell>')
# ========== 阶段名称标签 ==========
stage_labels = [
(start_x + 4*cycle_w, "命令 (0x3B)", "#4CAF50"),
(start_x + 12*cycle_w, "地址 [23:16]", "#666666"),
(start_x + 20*cycle_w, "地址 [15:8]", "#666666"),
(start_x + 28*cycle_w, "地址 [7:0]", "#666666"),
(start_x + 34*cycle_w, "数据 (Dual)", "#2196F3"),
]
for x, txt, color in stage_labels:
lines.append(f' <mxCell id="stage_{txt[:4]}" value="{txt}" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=13;fontStyle=1;fontColor={color};" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="{x-60}" y="235" width="120" height="20" as="geometry"/>')
lines.append(' </mxCell>')
# ========== 数据阶段 bit 标签 ==========
data_labels_io0 = ["D0","D2","D4","D6"]
data_labels_io1 = ["D1","D3","D5","D7"]
for i in range(4):
cx = rise_edges[32 + i]
# IO0
lines.append(f' <mxCell id="d0_{i}" value="{data_labels_io0[i]}" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontColor=#4CAF50;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="{cx-10}" y="255" width="20" height="20" as="geometry"/>')
lines.append(' </mxCell>')
# IO1
lines.append(f' <mxCell id="d1_{i}" value="{data_labels_io1[i]}" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontColor=#9C27B0;" vertex="1" parent="1">')
lines.append(f' <mxGeometry x="{cx-10}" y="355" width="20" height="20" as="geometry"/>')
lines.append(' </mxCell>')
lines.append(' </root>')
lines.append(' </mxGraphModel>')
lines.append(' </diagram>')
lines.append('</mxfile>')
return "\n".join(lines)
xml_content = make_qspi_mxfile()
output_path = "SPI_Waveform_QSPI.drawio"
with open(output_path, "w", encoding="utf-8") as f:
f.write(xml_content)
print("文件已生成:", output_path)