flask中的secure_filename方法获取不到中文文件名

在使用Flask时遇到一个问题,当尝试用secure_filename处理包含中文的文件名时,中文部分被过滤只剩后缀。原因是werkzeug库的此方法不支持中文。解决方案包括避免使用中文文件名或自定义secure_filename方法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

碰到以下问题:
将中文文件名传给 secure_filename 方法时所有的中文名都会被过滤掉,只剩下文件后缀名。
原因:werkzeug库的secure_filename方法中,中文被ignore或者压制导致数据缺失
解决方法:
1,要么更换或弃用中文文件名
2,视情况修改secure_filename方法的代码

def secure_filename(filename):
    r"""Pass it a filename and it will return a secure version of it.  This
    filename can then safely be stored on a regular file system and passed
    to :func:`os.path.join`.  The filename returned is an ASCII only string
    for maximum portability.

    On windows systems the function also makes sure that the file is not
    named after one of the special device files.

    >>> secure_filename("My cool movie.mov")
    'My_cool_movie.mov'
    >>> secure_filename("../../../etc/passwd")
    'etc_passwd'
    >>> secure_filename(
我现在有新LOG,但是发现源代码解析不出数据了。输出都是空的。请帮忙在源代码基础上修正。 新LOG片段如下,不用管LOG中的具体值是正式负,主要看格式。 [2025-07-31 21:26:28.200] n萔兄@刃兄`豥@蘊耯@z灶Pggddddd吕J蔲f豥鴍麼OTICE: Booting HSN2155 BL1 Firmware [2025-07-31 21:26:45.758] NOTICE: Built : 15:59:30, Feb 6 2025 [2025-07-31 21:26:45.760] NOTICE: Strap boot mode: 0xde [2025-07-31 21:26:45.762] NOTICE: SPI NAND pagesize: 2048 [2025-07-31 21:26:45.785] VERBOSE: secure_ctrl offset 112. [2025-07-31 21:26:45.788] INFO: Loading BL2 [2025-07-31 21:26:45.791] VERBOSE: Step size x20000 [2025-07-31 21:26:45.792] NOTICE: Booting @ flash address 0x0... [2025-07-31 21:26:45.795] VERBOSE: Header magic looks OK [2025-07-31 21:26:45.848] VERBOSE: Secure flags: 0x0 [2025-07-31 21:26:45.850] NOTICE: Secure boot enable: 0 [2025-07-31 21:26:45.869] VERBOSE: Payload check integrity succeeded [2025-07-31 21:26:45.896] [2025-07-31 21:26:45.896] ###### Hsn2155 Set PLL And Init DDR ###### [2025-07-31 21:26:45.900] Set pcm pll succ! [2025-07-31 21:26:45.902] Set serdes pll succ! [2025-07-31 21:26:45.903] Set cpu pll 1.2G succ! [2025-07-31 21:26:45.905] DDR init succ! [2025-07-31 21:26:45.906] Switch CPU PLL [2025-07-31 21:26:45.908] Switch BUS DIVIDE [2025-07-31 21:26:46.330] zq value check : 0xc0c0c0c [2025-07-31 21:26:46.357] vref: 0x0 [2025-07-31 21:26:46.367] 0x38c: 1f [2025-07-31 21:26:46.379] PHY_160_zq_result : 0x0 [2025-07-31 21:26:46.381] PHY_280_wrlvl_byte0 : 0xff0080 [2025-07-31 21:26:46.384] PHY_380_wrlvl_byte1 : 0xff0080 [2025-07-31 21:26:46.387] PHY_284_gate_byte0 : 0x280 [2025-07-31 21:26:46.389] PHY_384_gate_byte1 : 0x285 [2025-07-31 21:26:46.391] PHY_2a0_read : 0x0 [2025-07-31 21:26:46.393] PHY_2a4_read : 0x0 [2025-07-31 21:26:46.395] PHY_294_read : 0x7f7f7f7f [2025-07-31 21:26:46.397] PHY_298_read : 0x7f7f7f7f [2025-07-31 21:26:46.402] dq0 max_read_point : 0 [2025-07-31 21:26:46.402] dq0 min_read_point : 127 [2025-07-31 21:26:46.404] dq0 read_windows : -127 [2025-07-31 21:26:46.406] dq1 max_read_point : 0 [2025-07-31 21:26:46.408] dq1 min_read_point : 127 [2025-07-31 21:26:46.410] dq1 read_windows : -127 [2025-07-31 21:26:46.413] dq2 max_read_point : 0 [2025-07-31 21:26:46.416] dq2 min_read_point : 127 [2025-07-31 21:26:46.417] dq2 read_windows : -127 [2025-07-31 21:26:46.419] dq3 max_read_point : 0 [2025-07-31 21:26:46.422] dq3 min_read_point : 127 [2025-07-31 21:26:46.424] dq3 read_windows : -127 [2025-07-31 21:26:46.426] dq4 max_read_point : 0 [2025-07-31 21:26:46.427] dq4 min_read_point : 127 [2025-07-31 21:26:46.431] dq4 read_windows : -127 [2025-07-31 21:26:46.432] dq5 max_read_point : 0 [2025-07-31 21:26:46.435] dq5 min_read_point : 127 [2025-07-31 21:26:46.437] dq5 read_windows : -127 [2025-07-31 21:26:46.439] dq6 max_read_point : 0 [2025-07-31 21:26:46.441] dq6 min_read_point : 127 [2025-07-31 21:26:46.443] dq6 read_windows : -127 [2025-07-31 21:26:46.445] dq7 max_read_point : 0 [2025-07-31 21:26:46.447] dq7 min_read_point : 127 [2025-07-31 21:26:46.450] dq7 read_windows : -127 [2025-07-31 21:26:46.451] PHY_3a0_read : 0x0 [2025-07-31 21:26:46.453] PHY_3a4_read : 0x0 [2025-07-31 21:26:46.455] PHY_394_read : 0x7f7f7f7f [2025-07-31 21:26:46.458] PHY_398_read : 0x7f7f7f7f [2025-07-31 21:26:46.459] dq8 max_read_point : 0 [2025-07-31 21:26:46.462] dq8 min_read_point : 127 [2025-07-31 21:26:46.464] dq8 read_windows : -127 [2025-07-31 21:26:46.467] dq9 max_read_point : 0 [2025-07-31 21:26:46.469] dq9 min_read_point : 127 [2025-07-31 21:26:46.471] dq9 read_windows : -127 [2025-07-31 21:26:46.473] dq10 max_read_point : 0 [2025-07-31 21:26:46.475] dq10 min_read_point : 127 [2025-07-31 21:26:46.477] dq10 read_windows : -127 [2025-07-31 21:26:46.480] dq11 max_read_point : 0 [2025-07-31 21:26:46.482] dq11 min_read_point : 127 [2025-07-31 21:26:46.484] dq11 read_windows : -127 [2025-07-31 21:26:46.486] dq12 max_read_point : 0 [2025-07-31 21:26:46.489] dq12 min_read_point : 127 [2025-07-31 21:26:46.491] dq12 read_windows : -127 [2025-07-31 21:26:46.493] dq13 max_read_point : 0 [2025-07-31 21:26:46.495] dq13 min_read_point : 127 [2025-07-31 21:26:46.498] dq13 read_windows : -127 [2025-07-31 21:26:46.500] dq14 max_read_point : 0 [2025-07-31 21:26:46.502] dq14 min_read_point : 127 [2025-07-31 21:26:46.504] dq14 read_windows : -127 [2025-07-31 21:26:46.507] dq15 max_read_point : 0 [2025-07-31 21:26:46.511] dq15 min_read_point : 127 [2025-07-31 21:26:46.512] dq15 read_windows : -127 [2025-07-31 21:26:46.513] PHY_2e0_read : 0x0 [2025-07-31 21:26:46.516] PHY_2e4_read : 0x0 [2025-07-31 21:26:46.517] PHY_2c4_read : 0xffffffff [2025-07-31 21:26:46.520] PHY_2c8_read : 0xffffffff [2025-07-31 21:26:46.522] dq0 max_write_point : 0 [2025-07-31 21:26:46.524] dq0 min_write_point : 255 [2025-07-31 21:26:46.527] dq0 write_windows : -255 [2025-07-31 21:26:46.529] dq1 max_write_point : 0 [2025-07-31 21:26:46.531] dq1 min_write_point : 255 [2025-07-31 21:26:46.533] dq1 write_windows : -255 [2025-07-31 21:26:46.536] dq2 max_write_point : 0 [2025-07-31 21:26:46.538] dq2 min_write_point : 255 [2025-07-31 21:26:46.540] dq2 write_windows : -255 [2025-07-31 21:26:46.542] dq3 max_write_point : 0 [2025-07-31 21:26:46.545] dq3 min_write_point : 255 [2025-07-31 21:26:46.547] dq3 write_windows : -255 [2025-07-31 21:26:46.549] dq4 max_write_point : 0 [2025-07-31 21:26:46.551] dq4 min_write_point : 255 [2025-07-31 21:26:46.554] dq4 write_windows : -255 [2025-07-31 21:26:46.556] dq5 max_write_point : 0 [2025-07-31 21:26:46.558] dq5 min_write_point : 255 [2025-07-31 21:26:46.560] dq5 write_windows : -255 [2025-07-31 21:26:46.563] dq6 max_write_point : 0 [2025-07-31 21:26:46.565] dq6 min_write_point : 255 [2025-07-31 21:26:46.567] dq6 write_windows : -255 [2025-07-31 21:26:46.569] dq7 max_write_point : 0 [2025-07-31 21:26:46.572] dq7 min_write_point : 255 [2025-07-31 21:26:46.574] dq7 write_windows : -255 [2025-07-31 21:26:46.576] PHY_3e0_read : 0x0 [2025-07-31 21:26:46.578] PHY_3e4_read : 0x0 [2025-07-31 21:26:46.580] PHY_3c4_read : 0xffffffff [2025-07-31 21:26:46.583] PHY_3c8_read : 0xffffffff [2025-07-31 21:26:46.585] dq8 max_write_point : 0 [2025-07-31 21:26:46.586] dq8 min_write_point : 255 [2025-07-31 21:26:46.589] dq8 write_windows : -255 [2025-07-31 21:26:46.592] dq9 max_write_point : 0 [2025-07-31 21:26:46.593] dq9 min_write_point : 255 [2025-07-31 21:26:46.596] dq9 write_windows : -255 [2025-07-31 21:26:46.597] dq10 max_write_point : 0 [2025-07-31 21:26:46.600] dq10 min_write_point : 255 [2025-07-31 21:26:46.602] dq10 write_windows : -255 [2025-07-31 21:26:46.606] dq11 max_write_point : 0 [2025-07-31 21:26:46.607] dq11 min_write_point : 255 [2025-07-31 21:26:46.610] dq11 write_windows : -255 [2025-07-31 21:26:46.612] dq12 max_write_point : 0 [2025-07-31 21:26:46.615] dq12 min_write_point : 255 [2025-07-31 21:26:46.617] dq12 write_windows : -255 [2025-07-31 21:26:46.620] dq13 max_write_point : 0 [2025-07-31 21:26:46.621] dq13 min_write_point : 255 [2025-07-31 21:26:46.624] dq13 write_windows : -255 [2025-07-31 21:26:46.626] dq14 max_write_point : 0 [2025-07-31 21:26:46.628] dq14 min_write_point : 255 [2025-07-31 21:26:46.631] dq14 write_windows : -255 [2025-07-31 21:26:46.633] dq15 max_write_point : 0 [2025-07-31 21:26:46.635] dq15 min_write_point : 255 [2025-07-31 21:26:46.638] dq15 write_windows : -255 [2025-07-31 21:26:47.014] zq value check : 0xc0c0c0c [2025-07-31 21:26:47.041] vref: 0x1 [2025-07-31 21:26:47.051] 0x38c: 1f [2025-07-31 21:26:47.064] PHY_160_zq_result : 0x0 [2025-07-31 21:26:47.066] PHY_280_wrlvl_byte0 : 0xff0080 [2025-07-31 21:26:47.069] PHY_380_wrlvl_byte1 : 0xff0080 [2025-07-31 21:26:47.072] PHY_284_gate_byte0 : 0x280 [2025-07-31 21:26:47.074] PHY_384_gate_byte1 : 0x284 [2025-07-31 21:26:47.077] PHY_2a0_read : 0x0 [2025-07-31 21:26:47.078] PHY_2a4_read : 0x0 [2025-07-31 21:26:47.080] PHY_294_read : 0x7f7f7f7f [2025-07-31 21:26:47.082] PHY_298_read : 0x7f7f7f7f [2025-07-31 21:26:47.084] dq0 max_read_point : 0 [2025-07-31 21:26:47.086] dq0 min_read_point : 127 [2025-07-31 21:26:47.089] dq0 read_windows : -127 [2025-07-31 21:26:47.091] dq1 max_read_point : 0 [2025-07-31 21:26:47.093] dq1 min_read_point : 127 [2025-07-31 21:26:47.095] dq1 read_windows : -127 [2025-07-31 21:26:47.097] dq2 max_read_point : 0 [2025-07-31 21:26:47.099] dq2 min_read_point : 127 [2025-07-31 21:26:47.102] dq2 read_windows : -127 [2025-07-31 21:26:47.105] dq3 max_read_point : 0 [2025-07-31 21:26:47.107] dq3 min_read_point : 127 [2025-07-31 21:26:47.109] dq3 read_windows : -127 [2025-07-31 21:26:47.110] dq4 max_read_point : 0 [2025-07-31 21:26:47.113] dq4 min_read_point : 127 [2025-07-31 21:26:47.115] dq4 read_windows : -127 [2025-07-31 21:26:47.117] dq5 max_read_point : 0 [2025-07-31 21:26:47.120] dq5 min_read_point : 127 [2025-07-31 21:26:47.122] dq5 read_windows : -127 [2025-07-31 21:26:47.123] dq6 max_read_point : 0 [2025-07-31 21:26:47.125] dq6 min_read_point : 127 [2025-07-31 21:26:47.129] dq6 read_windows : -127 [2025-07-31 21:26:47.130] dq7 max_read_point : 0 [2025-07-31 21:26:47.132] dq7 min_read_point : 127 [2025-07-31 21:26:47.135] dq7 read_windows : -127 [2025-07-31 21:26:47.136] PHY_3a0_read : 0x0 [2025-07-31 21:26:47.138] PHY_3a4_read : 0x0 [2025-07-31 21:26:47.139] PHY_394_read : 0x7f7f7f7f [2025-07-31 21:26:47.142] PHY_398_read : 0x7f7f7f7f [2025-07-31 21:26:47.144] dq8 max_read_point : 0 [2025-07-31 21:26:47.146] dq8 min_read_point : 127 [2025-07-31 21:26:47.148] dq8 read_windows : -127 [2025-07-31 21:26:47.151] dq9 max_read_point : 0 [2025-07-31 21:26:47.153] dq9 min_read_point : 127 [2025-07-31 21:26:47.155] dq9 read_windows : -127 [2025-07-31 21:26:47.158] dq10 max_read_point : 0 [2025-07-31 21:26:47.159] dq10 min_read_point : 127 [2025-07-31 21:26:47.162] dq10 read_windows : -127 [2025-07-31 21:26:47.165] dq11 max_read_point : 0 [2025-07-31 21:26:47.166] dq11 min_read_point : 127 [2025-07-31 21:26:47.169] dq11 read_windows : -127 [2025-07-31 21:26:47.170] dq12 max_read_point : 0 [2025-07-31 21:26:47.174] dq12 min_read_point : 127 [2025-07-31 21:26:47.175] dq12 read_windows : -127 [2025-07-31 21:26:47.178] dq13 max_read_point : 0 [2025-07-31 21:26:47.180] dq13 min_read_point : 127 [2025-07-31 21:26:47.183] dq13 read_windows : -127 [2025-07-31 21:26:47.184] dq14 max_read_point : 0 [2025-07-31 21:26:47.187] dq14 min_read_point : 127 [2025-07-31 21:26:47.189] dq14 read_windows : -127 [2025-07-31 21:26:47.191] dq15 max_read_point : 0 [2025-07-31 21:26:47.194] dq15 min_read_point : 127 [2025-07-31 21:26:47.195] dq15 read_windows : -127 [2025-07-31 21:26:47.198] PHY_2e0_read : 0x0 [2025-07-31 21:26:47.200] PHY_2e4_read : 0x0 [2025-07-31 21:26:47.202] PHY_2c4_read : 0xffffffff [2025-07-31 21:26:47.204] PHY_2c8_read : 0xffffffff [2025-07-31 21:26:47.206] dq0 max_write_point : 0 [2025-07-31 21:26:47.208] dq0 min_write_point : 255 [2025-07-31 21:26:47.211] dq0 write_windows : -255 [2025-07-31 21:26:47.213] dq1 max_write_point : 0 [2025-07-31 21:26:47.215] dq1 min_write_point : 255 [2025-07-31 21:26:47.217] dq1 write_windows : -255 [2025-07-31 21:26:47.220] dq2 max_write_point : 0 [2025-07-31 21:26:47.222] dq2 min_write_point : 255 [2025-07-31 21:26:47.224] dq2 write_windows : -255 [2025-07-31 21:26:47.226] dq3 max_write_point : 0 [2025-07-31 21:26:47.229] dq3 min_write_point : 255 [2025-07-31 21:26:47.231] dq3 write_windows : -255 [2025-07-31 21:26:47.233] dq4 max_write_point : 0 [2025-07-31 21:26:47.235] dq4 min_write_point : 255 [2025-07-31 21:26:47.238] dq4 write_windows : -255 [2025-07-31 21:26:47.240] dq5 max_write_point : 0 [2025-07-31 21:26:47.243] dq5 min_write_point : 255 [2025-07-31 21:26:47.244] dq5 write_windows : -255 [2025-07-31 21:26:47.247] dq6 max_write_point : 0 [2025-07-31 21:26:47.249] dq6 min_write_point : 255 [2025-07-31 21:26:47.251] dq6 write_windows : -255 [2025-07-31 21:26:47.253] dq7 max_write_point : 0 [2025-07-31 21:26:47.256] dq7 min_write_point : 255 [2025-07-31 21:26:47.258] dq7 write_windows : -255 [2025-07-31 21:26:47.260] PHY_3e0_read : 0x0 [2025-07-31 21:26:47.262] PHY_3e4_read : 0x0 [2025-07-31 21:26:47.264] PHY_3c4_read : 0xffffffff [2025-07-31 21:26:47.267] PHY_3c8_read : 0xffffffff [2025-07-31 21:26:47.268] dq8 max_write_point : 0 [2025-07-31 21:26:47.271] dq8 min_write_point : 255 [2025-07-31 21:26:47.273] dq8 write_windows : -255 [2025-07-31 21:26:47.276] dq9 max_write_point : 0 [2025-07-31 21:26:47.278] dq9 min_write_point : 255 [2025-07-31 21:26:47.280] dq9 write_windows : -255 [2025-07-31 21:26:47.282] dq10 max_write_point : 0 [2025-07-31 21:26:47.285] dq10 min_write_point : 255 [2025-07-31 21:26:47.286] dq10 write_windows : -255 [2025-07-31 21:26:47.290] dq11 max_write_point : 0 [2025-07-31 21:26:47.291] dq11 min_write_point : 255 [2025-07-31 21:26:47.294] dq11 write_windows : -255 [2025-07-31 21:26:47.296] dq12 max_write_point : 0 [2025-07-31 21:26:47.299] dq12 min_write_point : 255 [2025-07-31 21:26:47.300] dq12 write_windows : -255 [2025-07-31 21:26:47.303] dq13 max_write_point : 0 [2025-07-31 21:26:47.306] dq13 min_write_point : 255 [2025-07-31 21:26:47.308] dq13 write_windows : -255 [2025-07-31 21:26:47.310] dq14 max_write_point : 0 [2025-07-31 21:26:47.313] dq14 min_write_point : 255 [2025-07-31 21:26:47.316] dq14 write_windows : -255 [2025-07-31 21:26:47.317] dq15 max_write_point : 0 [2025-07-31 21:26:47.319] dq15 min_write_point : 255 [2025-07-31 21:26:47.322] dq15 write_windows : -255 [2025-07-31 21:26:47.699] zq value check : 0xc0c0c0c 源代码如下: import numpy as np """ NumPy (Numerical Python) - 科学计算基础库 主要功能: - 提供高效的N维数组对象(ndarray) - 支持广播功能函数 - 提供线性代数、傅里叶变换、随机数生成等功能 在本代码中主要用于: - 数值计算和数组操作 - 科学计算支持 """ import matplotlib.pyplot as plt """ Matplotlib - Python中最强大的绘图库 主要功能: - 创建静态、动态和交互式图表 - 支持多种图表类型(线图、散点图、柱状图等) - 高度可定制化(颜色、线型、标签等) 在本代码中主要用于: - 绘制眼图 - 可视化DDR校准数据 - 创建图表和图形界面 """ import re """ re (Regular Expression) - 正则表达式模块 主要功能: - 文本搜索和模式匹配 - 文本替换 - 复杂字符串处理 在本代码中主要用于: - 解析日志文件中的关键数据 - 提取VREF、偏移量、数据点等信息 - 处理复杂的文本匹配任务 """ import datetime """ datetime - 日期和时间处理模块 主要功能: - 日期和时间的表示 - 日期和时间的计算 - 日期和时间的格式化 在本代码中主要用于: - 生成时间戳 - 创建带时间戳的文件名 - 记录报告生成时间 """ from matplotlib.lines import Line2D """ Line2D - 用于创建二维线条对象 主要功能: - 表示二维坐标系中的线条 - 控制线条属性(颜色、线宽、样式等) 在本代码中主要用于: - 创建图例元素 - 自定义图表中的线条样式 """ import os """ os (Operating System) - 操作系统接口模块 主要功能: - 文件和目录操作 - 路径操作 - 进程管理 在本代码中主要用于: - 文件路径处理 - 目录创建 - 文件存在性检查 """ from collections import defaultdict """ defaultdict - 带默认值的字典 主要功能: - 当访问不存在的键时返回默认值 - 避免KeyError异常 在本代码中主要用于: - 存储电压-窗口映射关系 - 简化字典初始化操作 """ import math """ math - 数学函数模块 主要功能: - 提供数学运算函数 - 包括三角函数、对数、取整等 在本代码中新增用于: - 计算中位数 """ # 健壮的文件读取函数 - 详细解释每个编程概念 def robust_read_file(file_path): """ 健壮的文件读取函数,处理不同编码的文件 参数: file_path - 文件在电脑上的完整路径(字符串) 编程概念详解: 1. 函数定义:def关键字用于定义函数,函数是一段可重复使用的代码块 2. 参数传递:file_path是形式参数,调用时传入实际文件路径 3. 异常处理:try-except结构用于捕获和处理运行时错误 4. 上下文管理器:with语句用于资源管理,确保文件正确关闭 5. 编码处理:不同文件可能使用不同编码(UTF-8, Latin-1等) 6. 正则表达式:用于过滤控制字符 """ ########################################################## # try-except 异常处理结构 # try: 尝试执行可能出错的代码块 ########################################################## try: ########################################################## # with 上下文管理器 # 语法:with expression [as variable]: # 特点:自动管理资源(如文件),确保资源正确释放 # open() 函数:打开文件 # 'r':只读模式 # encoding='utf-8':指定UTF-8编码 ########################################################## with open(file_path, 'r', encoding='utf-8') as f: # 文件对象方法:f.read()读取整个文件内容 return f.read() ########################################################## # except 捕获特定异常 # UnicodeDecodeError:当文件编码不匹配时抛出 ########################################################## except UnicodeDecodeError: try: # 尝试使用Latin-1编码(支持所有256个字节值) with open(file_path, 'r', encoding='latin-1') as f: content = f.read() ########################################################## # 正则表达式详解:r'[\x00-\x1F]+' # 用途:匹配并删除控制字符(ASCII 0-31) # 分解: # r'':原始字符串,避免转义字符处理 # []:字符类,匹配括号内任意字符 # \x00-\x1F:十六进制范围,表示ASCII控制字符(0-31) # +:量词,匹配1次或多次 # re.sub():替换匹配项 # 参数1:模式 # 参数2:替换内容(空字符串) # 参数3:输入字符串 ########################################################## return re.sub(r'[\x00-\x1F]+', '', content) ########################################################## # Exception 捕获所有异常 # 通用异常处理,打印错误信息 ########################################################## except Exception as e: # 格式化字符串:f-string (Python 3.6+) print(f"文件解码错误: {e}") return None # 日志解析函数 - 重点讲解正则表达式 def parse_log_file(log_content, normalization_point): """ 解析DDR校准日志文件,提取关键数据 参数: log_content - 日志文件的内容(字符串) normalization_point - 归一化点(十六进制整数) 数据结构说明: data = { vref: { dq_index: { 'read': (min, max, window), 'write': (min, max, window) } } } raw_data = { vref: { dq_index: { 'read': {'min': min_val, 'max': max_val}, 'write': {'min': min_val, 'max': max_val} } } } """ # 初始化数据结构 data = {} # 主数据结构,存储解析后的数据 current_vref = None # 当前处理的vref值 pending_data = {} # 临时存储待处理的数据(字典) current_offset = None # 当前偏移量 raw_data = {} # 存储原始数据(偏移前) # 按行处理日志内容 # 字符串方法:split('\n') 按换行符分割字符串 for line in log_content.split('\n'): # 字符串方法:strip() 移除首尾空白字符 line = line.strip() # 空行检查 if not line: continue # 跳过空行 ########################################################## # 正则表达式1:匹配VREF行 # 模式:r'.*vref:\s*0x([0-9a-fA-F]+)' # 目标示例: "Setting vref: 0x1A3" # # 详细分解: # .* - 匹配任意字符(除换行符外)0次或多次(贪婪匹配) # vref: - 匹配字面字符串 "vref:" # \s* - 匹配0个或多个空白字符(空格、制表符等) # 0x - 匹配字面字符串 "0x" # ( - 开始捕获组 # [0-9a-fA-F] - 字符类,匹配十六进制字符(0-9, a-f, A-F) # + - 匹配前面的元素1次或多次 # ) - 结束捕获组 # # 匹配过程: # "Setting vref: 0x1A3" -> 匹配整个字符串 # 捕获组1: "1A3" ########################################################## vref_match = re.match(r'.*vref:\s*0x([0-9a-fA-F]+)', line) if vref_match: # 获取捕获组内容 hex_str = vref_match.group(1) # int()函数:字符串转整数 # 参数1:字符串 # 参数2:基数(16表示十六进制) current_vref = int(hex_str, 16) # 字典初始化 data[current_vref] = {} # 嵌套字典初始化 raw_data[current_vref] = {} pending_data = {} # 重置临时数据 current_offset = None continue # 跳过后续处理 ########################################################## # 正则表达式2:匹配偏移量行 # 模式:r'.*0x38c:\s*(?:0x)?([0-9a-fA-F]+)' # 目标示例: "Offset 0x38c: 0x25" 或 "0x38c: 25" # # 详细分解: # .* - 匹配任意字符0次或多次 # 0x38c: - 匹配字面字符串 "0x38c:" # \s* - 匹配0个或多个空白字符 # (?: - 开始非捕获组 # 0x - 匹配字面字符串 "0x" # )? - 非捕获组出现0次或1次 # ( - 开始捕获组 # [0-9a-fA-F]+ - 匹配1个或多个十六进制字符 # ) - 结束捕获组 # # 特殊说明: # (?:...) 是非捕获组,匹配但不捕获内容 # 用于处理可选前缀而不创建额外捕获组 ########################################################## offset_match = re.match(r'.*0x38c:\s*(?:0x)?([0-9a-fA-F]+)', line) if offset_match and current_vref is not None: try: hex_str = offset_match.group(1) offset_value = int(hex_str, 16) # 计算偏移量:归一化点 - 读取值 current_offset = normalization_point - offset_value except ValueError: # 异常处理:打印警告信息 print(f"警告: 无法解析偏移量: {offset_match.group(1)}") current_offset = None continue ########################################################## # 正则表达式3:匹配最大值点 # 模式:r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)' # 目标示例: "dq5 max_read_point: 120" # # 详细分解: # .* - 匹配任意字符0次或多次 # dq - 匹配字面字符串 "dq" # (\d+) - 捕获组1:匹配1个或多个数字(DQ索引) # \s+ - 匹配1个或多个空白字符 # max_ - 匹配字面字符串 "max_" # (\w+) - 捕获组2:匹配1个或多个单词字符(方向:read/write) # _point - 匹配字面字符串 "point" # \s*:\s* - 匹配冒号前后任意空白 # (-?\d+) - 捕获组3:匹配可选负号后跟1个或多个数字 # # 捕获组说明: # 组1: DQ索引 (如 "5") # 组2: 方向 (如 "read") # 组3: 最大值 (如 "120") ########################################################## max_match = re.match(r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)', line) if max_match and current_vref is not None: # 提取捕获组内容 dq_index = int(max_match.group(1)) # 转换为整数 direction = max_match.group(2) # 字符串 max_val = int(max_match.group(3)) # 转换为整数 # 字典操作:检查键是否存在并初始化 if current_vref not in raw_data: # 字典设置默认值 raw_data[current_vref] = {} if dq_index not in raw_data[current_vref]: raw_data[current_vref][dq_index] = {} if direction not in raw_data[current_vref][dq_index]: # 嵌套字典初始化 raw_data[current_vref][dq_index][direction] = {'min': None, 'max': None} # 存储原始值(不应用偏移) raw_data[current_vref][dq_index][direction]['max'] = max_val # 只有读方向应用偏移 if direction == 'read' and current_offset is not None: # 应用偏移 max_val += current_offset # 存储到临时数据字典 key = (dq_index, direction) # 元组作为字典键 pending_data[key] = {'max': max_val} # 字典值也是字典 continue ########################################################## # 正则表达式4:匹配最小值点(结构类似最大值匹配) # 模式:r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)' # 目标示例: "dq5 min_read_point: 32" ########################################################## min_match = re.match(r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)', line) if min_match and current_vref is not None: dq_index = int(min_match.group(1)) direction = min_match.group(2) min_val = int(min_match.group(3)) key = (dq_index, direction) # 存储原始值(类似最大值处理) if current_vref not in raw_data: raw_data[current_vref] = {} if dq_index not in raw_data[current_vref]: raw_data[current_vref][dq_index] = {} if direction not in raw_data[current_vref][dq_index]: raw_data[current_vref][dq_index][direction] = {'min': None, 'max': None} raw_data[current_vref][dq_index][direction]['min'] = min_val # 只有读方向应用偏移 if direction == 'read' and current_offset is not None: min_val += current_offset # 更新临时数据 if key in pending_data: # 字典更新操作 pending_data[key]['min'] = min_val continue ########################################################## # 正则表达式5:匹配窗口行 # 模式:r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)' # 目标示例: "dq5 read_windows: 88" # # 详细分解: # .* - 匹配任意字符0次或多次 # dq - 匹配字面字符串 "dq" # (\d+) - 捕获组1:匹配1个或多个数字(DQ索引) # \s+ - 匹配1个或多个空白字符 # (\w+) - 捕获组2:匹配1个或多个单词字符(方向) # _windows - 匹配字面字符串 "_windows" # \s*:\s* - 匹配冒号前后任意空白 # (-?\d+) - 捕获组3:匹配可选负号后跟1个或多个数字 ########################################################## win_match = re.match(r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)', line) if win_match and current_vref is not None: dq_index = int(win_match.group(1)) direction = win_match.group(2) windows = int(win_match.group(3)) key = (dq_index, direction) # 检查是否已收集最小值和最大值 if key in pending_data and 'min' in pending_data[key] and 'max' in pending_data[key]: min_val = pending_data[key]['min'] max_val = pending_data[key]['max'] # 确定最大延迟值(读0x7F=127,写0xFF=255) max_delay = 0x7F if direction == 'read' else 0xFF # 确保值在有效范围内 min_val = max(0, min_val) # 最小值不小于0 max_val = min(max_delay, max_val) # 最大值不超过最大延迟 # 检查数据有效性 if min_val > max_val or windows < 0: result = None # 无效数据 else: # 计算窗口大小 window_size = max_val - min_val + 1 result = (min_val, max_val, window_size) # 存储到最终数据结构 if dq_index not in data[current_vref]: # 初始化嵌套字典 data[current_vref][dq_index] = {} data[current_vref][dq_index][direction] = result # 从临时数据中移除 del pending_data[key] # 删除字典键 # 返回解析结果 return data, raw_data # 眼图指标计算函数 - 算法详解(修改后) def calculate_eye_metrics(data, avddq, dq_index, direction): """ 计算眼图的最大宽度、最大高度以及中心点 参数: data - 解析后的日志数据(字典结构) avddq - AVDDQ电压值(浮点数) dq_index - DQ索引(0-15,整数) direction - 方向('read'或'write',字符串) 算法说明: 1. 遍历所有VREF值 2. 计算实际电压 = (vref / 0x1FF) * avddq 3. 获取当前DQ和方向的数据 4. 计算窗口大小(UI单位) 5. 确定最大眼宽(所有窗口中的最大值) 6. 计算最大眼高(连续电压范围的最大高度) 7. 计算眼图中心点(最大眼高和最大眼宽的交点) """ # 初始化变量 max_eye_width = 0.0 max_eye_height = 0.0 # 存储每个电压对应的窗口大小(用于计算眼高) voltage_windows = defaultdict(float) # 存储每个电压对应的延迟范围(用于计算眼宽) voltage_delay_ranges = {} # 存储每个延迟位置对应的电压范围(用于计算眼高) delay_voltage_ranges = defaultdict(list) # 确定最大延迟值(读0x7F=127,写0xFF=255) max_delay = 0x7F if direction == 'read' else 0xFF # 确定UI范围(读2UI,写4UI) ui_range = 2 if direction == 'read' else 4 # 遍历所有VREF值 for vref, dq_data in data.items(): # 计算实际电压 # 0x1FF = 511(9位最大值) voltage = (vref / 0x1FF) * avddq # 字典安全访问:get()方法 # 避免KeyError异常 dq_info = dq_data.get(dq_index, {}).get(direction) if dq_info is None: continue # 跳过无数据项 # 解包元组 min_point, max_point, window_size = dq_info # 重新计算窗口大小(确保正确性) window_size = max_point - min_point + 1 # 计算窗口大小(UI单位) window_ui = (window_size / max_delay) * ui_range # 更新最大眼宽 if window_ui > max_eye_width: max_eye_width = window_ui # 存储电压-窗口映射 voltage_windows[voltage] = window_ui # 存储电压-延迟范围映射(用于计算眼宽) voltage_delay_ranges[voltage] = (min_point, max_point) # 存储延迟位置对应的电压范围(用于计算眼高) for delay in range(min_point, max_point + 1): delay_voltage_ranges[delay].append(voltage) # 计算最大眼高(连续电压范围) # 步骤: # 1. 对电压排序 # 2. 遍历排序后的电压 # 3. 计算连续有效窗口的电压范围 sorted_voltages = sorted(voltage_windows.keys()) # 排序电压值 current_height = 0 # 当前连续高度 max_height = 0 # 最大高度 # 遍历排序后的电压(从第二个元素开始) for i in range(1, len(sorted_voltages)): # 计算电压差 voltage_diff = sorted_voltages[i] - sorted_voltages[i-1] # 检查相邻电压点是否都有有效窗口 # 字典键存在性检查 if sorted_voltages[i] in voltage_windows and sorted_voltages[i-1] in voltage_windows: current_height += voltage_diff if current_height > max_height: max_height = current_height else: current_height = 0 # 重置高度计数器 max_eye_height = max_height # 计算最大眼宽对应的延迟位置(新增) # 到具有最大窗口的电压点 best_voltage = None max_window_ui = 0 for voltage, window_ui in voltage_windows.items(): if window_ui > max_window_ui: max_window_ui = window_ui best_voltage = voltage # 计算最大眼高对应的延迟位置(新增) # 到具有最宽电压范围的延迟位置 best_delay = None max_voltage_range = 0 for delay, voltages in delay_voltage_ranges.items(): if voltages: min_v = min(voltages) max_v = max(voltages) voltage_range = max_v - min_v if voltage_range > max_voltage_range: max_voltage_range = voltage_range best_delay = delay # 计算眼图中心点 center_ui = None center_voltage = None if best_delay is not None and best_voltage is not None: # 将延迟转换为UI单位 center_ui = (best_delay / max_delay) * ui_range center_voltage = best_voltage # 返回计算结果 return max_eye_width, max_eye_height, center_ui, center_voltage, best_delay, best_voltage # 眼图数据生成函数 - 详细解释算法 def generate_eye_diagram(data, avddq, ui_ps, dq_index, direction): """ 生成眼图数据点 参数: data - 解析后的日志数据(字典) avddq - AVDDQ电压值(浮点数) ui_ps - 每个UI的时间(皮秒) dq_index - DQ索引(0-15,整数) direction - 方向('read'或'write',字符串) 算法说明: 1. 遍历所有VREF值 2. 计算实际电压 = (vref / 0x1FF) * avddq 3. 遍历所有可能的延迟值 4. 将延迟值转换为UI单位 5. 根据数据有效性标记为通过点或失败点 """ pass_points = [] # 存储通过点(绿色) fail_points = [] # 存储失败点(红色) # 确定最大延迟值(读0x7F=127,写0xFF=255) max_delay = 0x7F if direction == 'read' else 0xFF # 确定UI范围(读2UI,写4UI) ui_range = 2 if direction == 'read' else 4 # 遍历所有VREF值 for vref, dq_data in data.items(): # 计算实际电压 voltage = (vref / 0x1FF) * avddq # 获取当前DQ和方向的数据 dq_info = dq_data.get(dq_index, {}).get(direction) # 遍历所有可能的延迟值 for delay in range(0, max_delay + 1): # 将延迟值转换为UI单位 ui_value = (delay / max_delay) * ui_range # 如果没有有效数据,标记为失败点 if dq_info is None: fail_points.append((ui_value, voltage)) else: # 解包元组 min_point, max_point, _ = dq_info # 检查当前延迟是否在有效范围内 if min_point <= delay <= max_point: pass_points.append((ui_value, voltage)) else: fail_points.append((ui_value, voltage)) return pass_points, fail_points # 输出原始数据到新日志 - 文件操作详解 def export_raw_data(raw_data, normalization_point, log_path): """ 输出原始数据到新日志文件(按DQ划分) 参数: raw_data - 原始数据(偏移前) normalization_point - 归一化点 log_path - 原始日志文件路径 文件操作详解: 1. 创建输出目录:os.makedirs() 2. 构建文件路径:os.path.join() 3. 写入文件:open()配合write() 4. 格式化输出:f-string """ # 获取当前时间戳 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") # 获取日志文件名(不含扩展名) log_filename = os.path.basename(log_path) if '.' in log_filename: # rsplit() 从右边分割字符串,maxsplit=1表示只分割一次 log_name = log_filename.rsplit('.', 1)[0] else: log_name = log_filename # 创建输出目录 log_dir = os.path.dirname(log_path) or os.getcwd() # 获取目录或当前工作目录 output_dir = os.path.join(log_dir, "raw_data_export") # 创建输出目录路径 ########################################################## # os.makedirs() 创建目录(如果不存在) # exist_ok=True 表示目录已存在时不报错 ########################################################## os.makedirs(output_dir, exist_ok=True) # 创建输出文件路径 output_file = os.path.join(output_dir, f"{log_name}_raw_data.txt") # 写入原始数据 with open(output_file, 'w', encoding='utf-8') as f: # 写入标题信息 f.write("=" * 80 + "\n") f.write(f"DDR校准原始数据报告 (归一化点: 0x{normalization_point:X})\n") f.write(f"生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") f.write(f"原始日志: {log_path}\n") f.write("=" * 80 + "\n\n") # 按vref排序 sorted_vrefs = sorted(raw_data.keys()) for vref in sorted_vrefs: # 写入vref标题 f.write(f"VREF: 0x{vref:03X}\n") # :03X表示3位十六进制大写,不足补0 f.write("-" * 60 + "\n") # 按DQ索引排序 sorted_dq = sorted(raw_data[vref].keys()) for dq_index in sorted_dq: # 写入DQ标题 f.write(f" DQ{dq_index}:\n") # 处理读方向数据 if 'read' in raw_data[vref][dq_index]: rd = raw_data[vref][dq_index]['read'] f.write(f" 读方向:\n") f.write(f" 原始最小值: {rd['min']}\n") f.write(f" 原始最大值: {rd['max']}\n") # 计算并写入窗口大小 window_size = rd['max'] - rd['min'] + 1 f.write(f" 窗口大小: {window_size}\n") # 处理写方向数据 if 'write' in raw_data[vref][dq_index]: wr = raw_data[vref][dq_index]['write'] f.write(f" 写方向:\n") f.write(f" 原始最小值: {wr['min']}\n") f.write(f" 原始最大值: {wr['max']}\n") # 计算并写入窗口大小 window_size = wr['max'] - wr['min'] + 1 f.write(f" 窗口大小: {window_size}\n") f.write("\n") # DQ间空行 f.write("\n") # VREF间空行 print(f"原始数据已导出至: {output_file}") return output_file # 眼图绘制函数 - 数据可视化详解(修改后) def plot_eye_diagrams(log_content, data_rate, avddq, log_path, normalization_point): """ 绘制DDR眼图 参数: log_content - 日志内容 data_rate - 数据速率(Mbps) avddq - AVDDQ电压(V) log_path - 日志文件路径 normalization_point - 归一化点 主要修改: 1. 分组统计信息移到图顶部显示 2. 顶部显示两行分组统计信息 """ # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'Microsoft YaHei', 'WenQuanYi Micro Hei'] plt.rcParams['axes.unicode_minus'] = False # 计算UI时间(皮秒) ui_ps = (1 / (data_rate * 1e6)) * 1e12 # 解析日志文件 data, raw_data = parse_log_file(log_content, normalization_point) # 导出原始数据到新日志 raw_data_file = export_raw_data(raw_data, normalization_point, log_path) # 检查数据有效性 if not data: print("错误: 无法从日志中解析出有效数据") return None, None, None # 创建图表对象 fig_write, axes_write = plt.subplots(4, 4, figsize=(20, 20)) fig_read, axes_read = plt.subplots(4, 4, figsize=(20, 20)) # 设置标题 norm_title = f" (Normalized to 0x{normalization_point:X}, Raw Data: {os.path.basename(raw_data_file)})" fig_write.suptitle(f'DDR Write Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18) fig_read.suptitle(f'DDR Read Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18) # 展平坐标轴数组 axes_write = axes_write.flatten() axes_read = axes_read.flatten() # 创建图例元素 legend_elements = [ Line2D([0], [0], marker='o', color='w', label='Pass', markerfacecolor='green', markersize=10), Line2D([0], [0], marker='o', color='w', label='Fail', markerfacecolor='red', markersize=10) ] # 存储分组中心点数据(读眼图) group1_center_vrefs = [] # DQ0-DQ7 group1_center_delays = [] # DQ0-DQ7 group2_center_vrefs = [] # DQ8-DQ15 group2_center_delays = [] # DQ8-DQ15 # 遍历16个DQ通道 for dq_index in range(16): # 计算写眼图指标(不计算中心点) write_width, write_height, _, _, _, _ = calculate_eye_metrics(data, avddq, dq_index, 'write') # 生成写眼图数据点 write_pass, write_fail = generate_eye_diagram(data, avddq, ui_ps, dq_index, 'write') # 计算读眼图指标和中心点 read_width, read_height, read_center_ui, read_center_voltage, read_center_delay, read_center_vref = calculate_eye_metrics( data, avddq, dq_index, 'read' ) # 生成读眼图数据点 read_pass, read_fail = generate_eye_diagram(data, avddq, ui_ps, dq_index, 'read') # ================= 写眼图处理 ================= # 绘制写眼图 if write_fail: x_fail, y_fail = zip(*write_fail) axes_write[dq_index].scatter(x_fail, y_fail, s=1, c='red', alpha=0.1, zorder=1) if write_pass: x_pass, y_pass = zip(*write_pass) axes_write[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2) # 添加写眼图标注(仅显示最大眼宽和眼高) write_text = f"Max Eye Width: {write_width:.3f} UI\nMax Eye Height: {write_height:.3f} V" axes_write[dq_index].annotate( write_text, xy=(0.98, 0.02), xycoords='axes fraction', fontsize=9, ha='right', va='bottom', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8) ) # 设置写眼图轴属性 axes_write[dq_index].set_title(f'DQ{dq_index} Write Eye', fontsize=12) axes_write[dq_index].set_xlabel('Delay (UI)', fontsize=10) axes_write[dq_index].set_ylabel('Voltage (V)', fontsize=10) axes_write[dq_index].set_xlim(0, 4) # 写眼图0-4UI axes_write[dq_index].set_ylim(0, avddq) axes_write[dq_index].grid(True, linestyle='--', alpha=0.6) axes_write[dq_index].legend(handles=legend_elements, loc='upper right', fontsize=9) axes_write[dq_index].tick_params(axis='both', which='major', labelsize=9) # ================= 读眼图处理 ================= # 绘制读眼图 if read_fail: x_fail, y_fail = zip(*read_fail) axes_read[dq_index].scatter(x_fail, y_fail, s=1, c='red', alpha=0.1, zorder=1) if read_pass: x_pass, y_pass = zip(*read_pass) axes_read[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2) # 添加读眼图标注 read_text = f"Max Eye Width: {read_width:.3f} UI\nMax Eye Height: {read_height:.3f} V" axes_read[dq_index].annotate( read_text, xy=(0.98, 0.02), xycoords='axes fraction', fontsize=9, ha='right', va='bottom', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8) ) # 设置读眼图轴属性 axes_read[dq_index].set_title(f'DQ{dq_index} Read Eye', fontsize=12) axes_read[dq_index].set_xlabel('Delay (UI)', fontsize=10) axes_read[dq_index].set_ylabel('Voltage (V)', fontsize=10) axes_read[dq_index].set_xlim(0, 2) # 读眼图0-2UI axes_read[dq_index].set_ylim(0, avddq) axes_read[dq_index].grid(True, linestyle='--', alpha=0.6) axes_read[dq_index].legend(handles=legend_elements, loc='upper right', fontsize=9) axes_read[dq_index].tick_params(axis='both', which='major', labelsize=9) # 绘制读眼图中心点和辅助线 if read_center_ui is not None and read_center_voltage is not None: # 绘制中心点 axes_read[dq_index].scatter( [read_center_ui], [read_center_voltage], s=100, marker='*', c='yellow', edgecolors='black', zorder=10 ) # 计算原始Vref值 original_vref = int(round((read_center_voltage * 0x1FF) / avddq)) # 添加中心点标注 center_text = f"Center: ({read_center_ui:.3f} UI, {read_center_voltage:.3f} V)\n" \ f"Raw: Vref=0x{original_vref:X}, Delay={read_center_delay}" axes_read[dq_index].annotate( center_text, xy=(read_center_ui, read_center_voltage), xytext=(read_center_ui + 0.1, read_center_voltage + 0.05), arrowprops=dict(facecolor='black', shrink=0.05), fontsize=8, ha='left' ) # 绘制辅助线:最大眼宽竖线(蓝色虚线) axes_read[dq_index].axvline( x=read_center_ui, color='blue', linestyle='--', alpha=0.7, label=f'Max Width Line' ) # 绘制辅助线:最大眼高横线(蓝色虚线) axes_read[dq_index].axhline( y=read_center_voltage, color='blue', linestyle='--', alpha=0.7, label=f'Max Height Line' ) # 添加辅助线图例 line_legend = [ Line2D([0], [0], color='blue', linestyle='--', label='Max Width Line'), Line2D([0], [0], color='blue', linestyle='--', label='Max Height Line') ] axes_read[dq_index].legend(handles=legend_elements + line_legend, loc='upper right', fontsize=9) # 根据DQ索引分组存储中心点数据 if dq_index < 8: group1_center_vrefs.append(original_vref) group1_center_delays.append(read_center_delay) else: group2_center_vrefs.append(original_vref) group2_center_delays.append(read_center_delay) # 计算分组统计值 def calculate_group_stats(vrefs, delays): """计算一组中心点的统计值""" if not vrefs: return None, None, None, None # 计算Vref平均值和中位数 avg_vref = sum(vrefs) / len(vrefs) sorted_vrefs = sorted(vrefs) mid = len(sorted_vrefs) // 2 if len(sorted_vrefs) % 2 == 0: median_vref = (sorted_vrefs[mid-1] + sorted_vrefs[mid]) / 2 else: median_vref = sorted_vrefs[mid] # 计算延迟平均值和中位数 avg_delay = sum(delays) / len(delays) sorted_delays = sorted(delays) mid = len(sorted_delays) // 2 if len(sorted_delays) % 2 == 0: median_delay = (sorted_delays[mid-1] + sorted_delays[mid]) / 2 else: median_delay = sorted_delays[mid] return avg_vref, median_vref, avg_delay, median_delay # 计算第一组(DQ0-DQ7)的统计值 stats1 = calculate_group_stats(group1_center_vrefs, group1_center_delays) # 计算第二组(DQ8-DQ15)的统计值 stats2 = calculate_group_stats(group2_center_vrefs, group2_center_delays) # 在图像顶部添加分组汇总信息(两行) if stats1[0] is not None: avg_vref1, median_vref1, avg_delay1, median_delay1 = stats1 # 第一组文本(DQ0-DQ7) summary_text1 = f"DQ0-DQ7 Center Points: " \ f"Avg Vref=0x{int(round(avg_vref1)):X}, " \ f"Median Vref=0x{int(median_vref1):X}, " \ f"Avg Delay={avg_delay1:.1f}, " \ f"Median Delay={median_delay1:.1f}" # 位置:0.95(顶部) fig_read.text(0.5, 0.95, summary_text1, ha='center', fontsize=12, bbox=dict(facecolor='white', alpha=0.8)) if stats2[0] is not None: avg_vref2, median_vref2, avg_delay2, median_delay2 = stats2 # 第二组文本(DQ8-DQ15) summary_text2 = f"DQ8-DQ15 Center Points: " \ f"Avg Vref=0x{int(round(avg_vref2)):X}, " \ f"Median Vref=0x{int(median_vref2):X}, " \ f"Avg Delay={avg_delay2:.1f}, " \ f"Median Delay={median_delay2:.1f}" # 位置:0.92(在上一行下方) fig_read.text(0.5, 0.92, summary_text2, ha='center', fontsize=12, bbox=dict(facecolor='white', alpha=0.8)) # 调整布局 fig_write.tight_layout(rect=[0, 0, 1, 0.96]) # 为读眼图顶部留出空间 fig_read.tight_layout(rect=[0, 0, 1, 0.90]) # 文件路径处理(添加时间戳) log_dir = os.path.dirname(log_path) or os.getcwd() timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") log_filename = os.path.basename(log_path) log_name = log_filename.rsplit('.', 1)[0] if '.' in log_filename else log_filename # 构建输出文件路径(写眼图只加时间戳) write_filename = os.path.join(log_dir, f"{log_name}_ddr_write_eye_{timestamp}.png") # 构建读眼图文件名(包含两组VREF平均值) group1_avg_vref = int(round(stats1[0])) if stats1[0] is not None else 0 group2_avg_vref = int(round(stats2[0])) if stats2[0] is not None else 0 read_filename = os.path.join( log_dir, f"{log_name}_ddr_read_eye_{timestamp}_G1Vref_0x{group1_avg_vref:X}_G2Vref_0x{group2_avg_vref:X}.png" ) # 保存图像 fig_write.savefig(write_filename, dpi=300, bbox_inches='tight') fig_read.savefig(read_filename, dpi=300, bbox_inches='tight') # 关闭图像释放内存 plt.close(fig_write) plt.close(fig_read) # 打印结果 print(f"写眼图已保存至: {write_filename}") print(f"读眼图已保存至: {read_filename}") return write_filename, read_filename, raw_data_file # 主函数 - 程序入口点详解 def main(): """ 主函数,程序入口点 功能: - 获取用户输入 - 读取日志文件 - 解析数据 - 生成眼图 - 导出结果 用户交互详解: 1. 使用input()获取用户输入 2. 使用循环处理无效输入 3. 使用try-except捕获异常 """ # 打印欢迎信息 print("=" * 50) print("DDR眼图生成器(带原始数据导出)") print("=" * 50) # 用户输入DataRate(带异常处理) while True: try: data_rate = float(input("请输入DataRate (Mbps/Pin): ")) break except ValueError: print("错误: 请输入有效的数字") # 用户输入AVDDQ电压(带异常处理) while True: try: avddq = float(input("请输入AVDDQ电压值 (V): ")) break except ValueError: print("错误: 请输入有效的数字") # 归一化点输入处理(带错误检查) while True: norm_input = input("请输入归一化点(十六进制值,如0x40或40): ").strip() if not norm_input: print("错误: 输入不能为空,请重新输入") continue try: # 处理十六进制前缀 if norm_input.startswith(("0x", "0X")): hex_str = norm_input[2:] else: hex_str = norm_input # 字符串转整数(16进制) normalization_point = int(hex_str, 16) break except ValueError: print(f"错误: '{norm_input}' 不是有效的十六进制数,请重新输入") # 日志文件路径输入(带文件存在检查) while True: log_path = input("请输入日志文件路径: ").strip() # 检查文件是否存在 # os.path.exists() 判断路径是否存在 if not os.path.exists(log_path): print(f"错误: 文件 '{log_path}' 不存在,请重新输入") else: # 获取绝对路径 log_path = os.path.abspath(log_path) break # 读取文件内容 log_content = robust_read_file(log_path) if log_content is None: print("无法读取日志文件") return # 尝试生成眼图(带异常处理) try: # 调用眼图生成函数(返回三个值) write_file, read_file, raw_data_file = plot_eye_diagrams( log_content, data_rate, avddq, log_path, normalization_point ) print("\n眼图生成成功!") print(f"原始数据文件: {raw_data_file}") except Exception as e: # 捕获所有异常并打印错误信息 print(f"眼图生成失败: {e}") # 异常对象:e.args 获取异常参数 print(f"错误详情: {e.args}") # Python特殊检查 - 模块执行控制 if __name__ == "__main__": """ __name__ 是Python的内置变量 当脚本直接运行时,__name__ 等于 "__main__" 当脚本被导入时,__name__ 等于模块名 这种结构允许: 1. 直接运行脚本时执行测试代码 2. 作为模块导入时不执行测试代码 """ main() # 调用主函数
最新发布
08-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值