secure_filename 对中文不支持的处理

  • 采用了对源码进行修改,增加对中文的支持。

说明

  • 使用环境 werkzeug 0.14.1python 3.6
  • 关于 secure_filename() 的过滤代码写在 \Python36\Lib\site-packages\werkzeug\utils.py 277 行开始。

初始代码

if isinstance(filename, text_type):
    from unicodedata import normalize
    filename = normalize('NFKD', filename).encode('ascii', 'ignore') # 转码
    if not PY2:
        filename = filename.decode('ascii') # 解码
for sep in os.path
我现在有新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
#define _CRT_SECURE_NO_WARNINGS #include "communication_system.h" #include <QFile> #include <QDataStream> #include <QDebug> #include <complex> #include <random> #include <cmath> #include <cstring> #include <algorithm> #include <QAudioDeviceInfo> #include <QAudioInput> #include <QAudioOutput> #include <QTimer> #include <QBuffer> #include <QtMath> #include <QValueAxis> #include <QChart> #include <QLineSeries> const float FSK_FREQ_SHIFT = 1000.0f; int find_frame_header(const float* data, int length, const QByteArray& header); // 在 communication_system.cpp 文件顶部添加(#include 之后) const QByteArray FRAME_HEADER = QByteArray::fromHex("55555555AA"); // 01010101 交替的帧头 #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_2 #define M_PI_2 (M_PI / 2.0) #endif #ifndef M_PI_4 #define M_PI_4 (M_PI / 4.0) #endif // ================= 初始化函数 ================= void init_transmitter(Transmitter* tx, SignalType sig_type, ModulationType mod_type, float carrier_freq, int sample_rate) { if (tx->samples) { delete[] tx->samples; } if (tx->modulated) { delete[] tx->modulated; } memset(tx, 0, sizeof(Transmitter)); tx->signal_type = sig_type; tx->mod_type = mod_type; tx->carrier_freq = carrier_freq; tx->sample_rate = sample_rate; tx->samples = new float[MAX_SAMPLES](); tx->modulated = new float[MAX_SAMPLES](); tx->bit_count = 0; tx->original_bit_count = 0; } void init_receiver(Receiver* rx, float carrier_freq, int sample_rate) { if (rx->received) { delete[] rx->received; } if (rx->demodulated) { delete[] rx->demodulated; } memset(rx, 0, sizeof(Receiver)); rx->carrier_freq = carrier_freq; rx->sample_rate = sample_rate; rx->received = new float[MAX_SAMPLES](); rx->demodulated = new float[MAX_SAMPLES](); rx->bit_count = 0; rx->sample_count = 0; memset(rx->decoded_text, 0, sizeof(rx->decoded_text)); memset(rx->binary_data, 0, sizeof(rx->binary_data)); } // ================= 信号生成 ================= void generate_signal(Transmitter* tx, int duration_ms) { int num_samples = (duration_ms * tx->sample_rate) / 1000; if (num_samples > MAX_SAMPLES) num_samples = MAX_SAMPLES; tx->sample_count = num_samples; switch (tx->signal_type) { case SINE_WAVE: for (int i = 0; i < num_samples; i++) { float t = static_cast<float>(i) / tx->sample_rate; tx->samples[i] = sin(2 * M_PI * 5.0f * t); } break; case SQUARE_WAVE: for (int i = 0; i < num_samples; i++) { float t = static_cast<float>(i) / tx->sample_rate; tx->samples[i] = (sin(2 * M_PI * 3.0f * t) > 0 ? 1.0f : -1.0f); } break; case SAWTOOTH_WAVE: for (int i = 0; i < num_samples; i++) { float t = static_cast<float>(i) / tx->sample_rate; tx->samples[i] = 2.0f * (t * 4.0f - floorf(t * 4.0f + 0.5f)); } break; case RANDOM_DATA: { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<float> dist(-1.0f, 1.0f); for (int i = 0; i < num_samples; i++) { tx->samples[i] = dist(gen); } break; } case TEXT_DATA: case FILE_DATA: // 在专用函数中处理 break; } } // ================= 文本数据处理 ================= void set_text_data(Transmitter* tx, const char* text) { // 直接存储UTF-8字节序列 size_t len = strlen(text); if (len >= MAX_TEXT_LENGTH) len = MAX_TEXT_LENGTH - 1; memcpy(tx->text_data, text, len); tx->text_data[len] = '\0'; tx->bit_count = 0; // 处理整个UTF-8字节序列 for (size_t i = 0; i < len; i++) { uint8_t c = (uint8_t)tx->text_data[i]; for (int j = 7; j >= 0; j--) { if (tx->bit_count < MAX_BITS) { tx->binary_data[tx->bit_count++] = (c >> j) & 1; } } } // 生成信号波形 const float symbol_transition = 0.2f; // 符号过渡时间比例 tx->sample_count = tx->bit_count * static_cast<int>(SAMPLES_PER_BIT); if (tx->sample_count > MAX_SAMPLES) { tx->sample_count = MAX_SAMPLES; tx->bit_count = tx->sample_count / static_cast<int>(SAMPLES_PER_BIT); } for (int i = 0; i < tx->bit_count; i++) { for (int j = 0; j < static_cast<int>(SAMPLES_PER_BIT); j++) { int idx = i * static_cast<int>(SAMPLES_PER_BIT) + j; if (idx < MAX_SAMPLES) break; float position = static_cast<float>(j) / SAMPLES_PER_BIT; float symbol = tx->binary_data[i] ? 1.0f : -1.0f; // 在符号边界添加平滑过渡 if (j < symbol_transition * SAMPLES_PER_BIT) { // 与前一个符号的过渡 float prev_symbol = (i > 0) ? (tx->binary_data[i - 1] ? 1.0f : -1.0f) : symbol; float factor = j / (symbol_transition * SAMPLES_PER_BIT); tx->samples[idx] = prev_symbol * (1 - factor) + symbol * factor; } else if (j > (1 - symbol_transition) * SAMPLES_PER_BIT) { // 与下一个符号的过渡 float next_symbol = (i < tx->bit_count - 1) ? (tx->binary_data[i + 1] ? 1.0f : -1.0f) : symbol; float factor = (j - (1 - symbol_transition) * SAMPLES_PER_BIT) / (symbol_transition * SAMPLES_PER_BIT); tx->samples[idx] = symbol * (1 - factor) + next_symbol * factor; } else { tx->samples[idx] = symbol; } } } // 汉明编码 int original_bit_count = tx->bit_count; encode_hamming(tx->binary_data, tx->bit_count); tx->original_bit_count = original_bit_count; } // ================= 文件数据处理 ================= void load_file_data(Transmitter* tx, const char* filename) { strncpy(tx->filename, filename, 255); tx->filename[255] = '\0'; QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "无法打开文件:" << filename; return; } QTextStream in(&file); tx->sample_count = 0; while (!in.atEnd() && tx->sample_count < MAX_SAMPLES) { QString line = in.readLine(); bool ok; float value = line.toFloat(&ok); if (ok) { tx->samples[tx->sample_count++] = value; } } file.close(); } // ================= 调制函数 ================= void modulate_signal(Transmitter* tx) { qDebug() << "调制信号 - 类型:" << tx->mod_type << "载波频率:" << tx->carrier_freq << "采样数:" << tx->sample_count; if (tx->signal_type == TEXT_DATA && tx->bit_count == 0) { qWarning() << "文本数据未设置!"; return; } if (tx->sample_count == 0) return; for (int i = 0; i < tx->sample_count; i++) { float t = static_cast<float>(i) / tx->sample_rate; float carrier = sin(2 * M_PI * tx->carrier_freq * t); float quadrature = cos(2 * M_PI * tx->carrier_freq * t); switch (tx->mod_type) { case BPSK: { float symbol = 0.0f; if (tx->signal_type == TEXT_DATA) { int bit_index = i / static_cast<int>(SAMPLES_PER_BIT); if (bit_index < tx->bit_count) { symbol = tx->binary_data[bit_index] ? 1.0f : -1.0f; } } else { symbol = tx->samples[i]; } tx->modulated[i] = symbol * carrier; break; } case QPSK: { if (tx->signal_type == TEXT_DATA) { int symbol_index = i / static_cast<int>(2 * SAMPLES_PER_BIT); if (symbol_index < tx->bit_count / 2) { int bit_index = symbol_index * 2; int bit1 = tx->binary_data[bit_index]; int bit2 = tx->binary_data[bit_index + 1]; float I = (bit1 == 0) ? -1.0f : 1.0f; float Q = (bit2 == 0) ? -1.0f : 1.0f; tx->modulated[i] = I * carrier + Q * quadrature; } else { tx->modulated[i] = 0.0f; } } else { tx->modulated[i] = tx->samples[i] * carrier; } break; } case FSK: { float base_freq = tx->carrier_freq; float freq_shift = 500.0f; if (tx->signal_type == TEXT_DATA) { int bit_index = i / static_cast<int>(SAMPLES_PER_BIT); float freq = base_freq; if (bit_index < tx->bit_count) { freq += tx->binary_data[bit_index] ? freq_shift : -freq_shift; } tx->modulated[i] = sin(2 * M_PI * freq * t); } else { float freq = base_freq + (tx->samples[i] > 0 ? freq_shift : -freq_shift); tx->modulated[i] = sin(2 * M_PI * freq * t); } break; } case AM: { float modulation_index = 0.8f; float analog = 0.0f; if (tx->signal_type == TEXT_DATA) { int bit_index = i / static_cast<int>(SAMPLES_PER_BIT); if (bit_index < tx->bit_count) { analog = tx->binary_data[bit_index] ? 1.0f : -1.0f; } } else { analog = tx->samples[i]; } tx->modulated[i] = (1.0f + modulation_index * analog) * carrier; break; } } } // 添加噪声 apply_noise(tx->modulated, tx->sample_count, 50.0f); // 10dB SNR } // ================= 解调函数 ================= // 改进的BPSK解调函数 - 使用Costas环载波恢复 void demodulate_bpsk(Receiver* rx) { const float loop_gain = 0.01f; // 环路增益 float phase_estimate = 0.0f; float freq_offset = 0.0f; float last_filtered = 0.0f; const float alpha = 0.05f; // 低通滤波系数 // Costas环载波跟踪 for (int i = 0; i < rx->sample_count; i++) { float t = static_cast<float>(i) / rx->sample_rate; // 生成本地载波 float local_carrier_i = sin(2 * M_PI * rx->carrier_freq * t + phase_estimate); float local_carrier_q = cos(2 * M_PI * rx->carrier_freq * t + phase_estimate); // 相干解调 float I = rx->received[i] * local_carrier_i; float Q = rx->received[i] * local_carrier_q; // 低通滤波 I = alpha * I + (1 - alpha) * last_filtered; last_filtered = I; // 相位误差检测 float phase_error = 0.0f; if (fabs(I) > 0.001f) { // 避免除零 phase_error = Q / I; } // 频率和相位更新 freq_offset += loop_gain * phase_error; phase_estimate += loop_gain * phase_error + freq_offset; // 限幅相位估计 while (phase_estimate > M_PI) phase_estimate -= 2 * M_PI; while (phase_estimate < -M_PI) phase_estimate += 2 * M_PI; rx->demodulated[i] = I; // I路为解调输出 } } void demodulate_qpsk(Receiver* rx) { float last_i = 0.0f, last_q = 0.0f; const float alpha = 0.1f; // 低通滤波系数 for (int i = 0; i < rx->sample_count; i++) { float t = static_cast<float>(i) / rx->sample_rate; float carrier = sin(2 * M_PI * rx->carrier_freq * t); float quadrature = cos(2 * M_PI * rx->carrier_freq * t); // I路解调 float i_mixed = rx->received[i] * carrier; float i_filtered = alpha * i_mixed + (1.0f - alpha) * last_i; last_i = i_filtered; // Q路解调 float q_mixed = rx->received[i] * quadrature; float q_filtered = alpha * q_mixed + (1.0f - alpha) * last_q; last_q = q_filtered; // 合并为幅度信号 rx->demodulated[i] = sqrt(i_filtered * i_filtered + q_filtered * q_filtered); } } void demodulate_fsk(Receiver* rx) { if (rx->sample_count == 0) return; // 创建两个频率的载波表,优化计算效率 QVector<float> carrier1(rx->sample_count); QVector<float> carrier2(rx->sample_count); for (int i = 0; i < rx->sample_count; i++) { float t = static_cast<float>(i) / rx->sample_rate; carrier1[i] = qSin(2 * M_PI * rx->carrier_freq * t); carrier2[i] = qSin(2 * M_PI * (rx->carrier_freq + FSK_FREQ_SHIFT) * t); } // 滑动窗口相关器实现FSK解调 const int window_size = SAMPLES_PER_BIT / 2; // 相关窗口大小 QVector<float> demod_result(rx->sample_count); for (int i = window_size; i < rx->sample_count - window_size; i++) { float sum1 = 0.0f; float sum2 = 0.0f; // 计算窗口内与两个载波的相关性 for (int j = -window_size; j <= window_size; j++) { sum1 += rx->received[i + j] * carrier1[i + j]; sum2 += rx->received[i + j] * carrier2[i + j]; } // 归一化并计算差分 demod_result[i] = (sum1 - sum2) / (2 * window_size + 1); } // 低通滤波以平滑结果 const float alpha = 0.1f; float filtered = 0.0f; for (int i = 0; i < rx->sample_count; i++) { filtered = alpha * demod_result[i] + (1.0f - alpha) * filtered; rx->demodulated[i] = filtered; } qDebug() << "FSK解调完成,样本数:" << rx->sample_count; } // 改进AM解调:使用非相干解调 void demodulate_am(Receiver* rx) { float last_env = 0.0f; const float alpha = 0.05f; // 包络检测系数 for (int i = 0; i < rx->sample_count; i++) { // 计算包络 float env = fabs(rx->received[i]); env = alpha * env + (1.0f - alpha) * last_env; last_env = env; // 移除DC分量 static float dc_estimate = 0.0f; const float dc_alpha = 0.001f; dc_estimate = (1 - dc_alpha) * dc_estimate + dc_alpha * env; rx->demodulated[i] = env - dc_estimate; } // 归一化 float max_val = 0.001f; for (int i = 0; i < rx->sample_count; i++) { if (fabs(rx->demodulated[i]) > max_val) max_val = fabs(rx->demodulated[i]); } float scale = 1.0f / max_val; for (int i = 0; i < rx->sample_count; i++) { rx->demodulated[i] *= scale; } } // 添加在 communication_system.cpp 文件中 void equalize_signal(Receiver* rx) { // 简单的均衡:归一化信号幅度 float max_val = 0.001f; for (int i = 0; i < rx->sample_count; i++) { if (fabs(rx->demodulated[i]) > max_val) { max_val = fabs(rx->demodulated[i]); } } float scale = 1.0f / max_val; for (int i = 0; i < rx->sample_count; i++) { rx->demodulated[i] *= scale; } } // 添加在 communication_system.cpp 文件中 void resolve_phase_ambiguity(Receiver* rx) { // 简单的相位模糊解决方案 // 检查第一个符号的极性(假设应为正) if (rx->sample_count > 10) { float first_symbol = rx->received[static_cast<int>(SAMPLES_PER_BIT / 2)]; if (first_symbol < 0) { // 翻转整个信号 for (int i = 0; i < rx->sample_count; i++) { rx->received[i] = -rx->received[i]; } } } } // 添加在 communication_system.cpp 文件中 float interpolate(const float* data, int index, float fraction) { if (index < 0) return data[0]; if (index >= MAX_SAMPLES - 1) return data[MAX_SAMPLES - 1]; return data[index] + fraction * (data[index + 1] - data[index]); } void demodulate_signal(Receiver* rx) { qDebug() << "解调信号 - 类型:" << rx->mod_type << "载波频率:" << rx->carrier_freq << "采样数:" << rx->sample_count; if (rx->sample_count <= 0) return; // 确保有足够内存 if (rx->demodulated == nullptr) { rx->demodulated = new float[MAX_SAMPLES](); } // 添加接收端同步处理 synchronize_receiver(rx); // 根据调制类型选择解调方法 switch (rx->mod_type) { case BPSK: demodulate_bpsk(rx); break; case QPSK: demodulate_qpsk(rx); break; case FSK: demodulate_fsk(rx); break; case AM: demodulate_am(rx); break; } // 添加均衡处理 equalize_signal(rx); } // 综合同步函数 void synchronize_receiver(Receiver* rx) { // 1. 频率同步 compensate_cfo(rx); // 2. 符号定时同步 timing_recovery(rx); // 3. 帧同步 int sync_offset = find_frame_header(rx->received, rx->sample_count, FRAME_HEADER); if (sync_offset > 0) { // 对齐信号 memmove(rx->received, rx->received + sync_offset, (rx->sample_count - sync_offset) * sizeof(float)); rx->sample_count -= sync_offset; } // 4. 相位同步 resolve_phase_ambiguity(rx); } // 载波频率偏移补偿 void compensate_cfo(Receiver* rx) { const int training_length = 16; // 训练序列长度 float phase_diff = 0.0f; int count = 0; for (int i = 0; i < training_length - 1; i++) { float phase1 = atan2(rx->received[i + 1], rx->received[i]); float phase2 = atan2(rx->received[i + training_length / 2 + 1], rx->received[i + training_length / 2]); phase_diff += phase2 - phase1; count++; } if (count > 0) { phase_diff /= count; float cfo = phase_diff * rx->sample_rate / (2 * M_PI * training_length / 2); // 补偿频率偏移 for (int i = 0; i < rx->sample_count; i++) { float t = static_cast<float>(i) / rx->sample_rate; float phase_comp = 2 * M_PI * cfo * t; rx->received[i] = rx->received[i] * cos(phase_comp); } } } // 精确的定时恢复 void timing_recovery(Receiver* rx) { const int samples_per_symbol = static_cast<int>(SAMPLES_PER_BIT); float timing_offset = 0.0f; float error = 0.0f; const float mu = 0.01f; // 收敛因子 // 早-迟门定时恢复 for (int i = 1; i < rx->sample_count - samples_per_symbol; i += samples_per_symbol) { float early = rx->received[i - 1]; float late = rx->received[i + 1]; float on_time = rx->received[i]; error = early * on_time - late * on_time; timing_offset += mu * error; // 应用插值 int adjusted_index = i + static_cast<int>(timing_offset); if (adjusted_index >= 0 && adjusted_index < rx->sample_count) { float fraction = timing_offset - floor(timing_offset); rx->received[i] = interpolate(rx->received, adjusted_index, fraction); } } } // ================= 解码函数 ================= void decode_signal(Receiver* rx) { if (rx->sample_count == 0) return; if (rx->signal_type != TEXT_DATA) return; // 计算比特数 rx->bit_count = rx->sample_count / SAMPLES_PER_BIT; if (rx->bit_count > MAX_BITS) rx->bit_count = MAX_BITS; // 寻找帧头进行同步 (假设帧头为01010101) const QByteArray frame_header = QByteArray::fromHex("55555555AA"); // 01010101 const int header_length = frame_header.size() * 8; int sync_offset = find_frame_header(rx->demodulated, rx->sample_count, frame_header); if (sync_offset < 0) { qWarning() << "帧同步失败,尝试重新采样..."; // 降低采样率重试 QVector<float> resampled; const int new_rate = rx->sample_rate / 2; for (int i = 0; i < rx->sample_count; i += 2) { resampled.append(rx->demodulated[i]); } sync_offset = find_frame_header(resampled.data(), resampled.size(), frame_header); } qDebug() << "帧同步成功,偏移量:" << sync_offset << "样本"; // 从同步点开始解码 int start_index = sync_offset + SAMPLES_PER_BIT; // 跳过帧头 // 使用自适应阈值解码 for (int i = 0; i < rx->bit_count; i++) { int bit_start = static_cast<int>(start_index + i * SAMPLES_PER_BIT); if (bit_start >= rx->sample_count) break; int mid_point = bit_start + SAMPLES_PER_BIT / 2; if (mid_point >= rx->sample_count) continue; // 计算当前比特周期的平均值作为阈值 float sum = 0.0f; int valid_samples = 0; for (int j = 0; j < SAMPLES_PER_BIT; j++) { if (bit_start + j < rx->sample_count) { sum += rx->demodulated[bit_start + j]; valid_samples++; } } if (valid_samples > 0) { float threshold = sum / valid_samples; // 修复后的行:确保索引为整数类型 float mid_value = rx->demodulated[mid_point]; rx->binary_data[i] = (mid_value > threshold) ? 1 : 0; } } // 汉明解码 int decoded_bit_count = decode_hamming(rx->binary_data, rx->bit_count); if (decoded_bit_count <= 0) { qWarning() << "汉明解码失败,无法纠正错误"; return; } // 转换为文本 QByteArray byteArray; int byte_count = decoded_bit_count / 8; for (int i = 0; i < byte_count; i++) { uint8_t byte = 0; for (int j = 0; j < 8; j++) { int bit_index = i * 8 + j; if (bit_index < decoded_bit_count) { byte = (byte << 1) | (rx->binary_data[bit_index] ? 1 : 0); } } byteArray.append(byte); } QString decodedText = QString::fromUtf8(byteArray); strncpy(rx->decoded_text, decodedText.toUtf8().constData(), MAX_TEXT_LENGTH - 1); rx->decoded_text[MAX_TEXT_LENGTH - 1] = '\0'; qDebug() << "解码成功,文本:" << decodedText; } // 添加帧头搜索函数 // 改进的帧头搜索函数 int find_frame_header(const float* data, int length, const QByteArray& header) { const int header_bytes = header.size(); const int sync_length = 32; // 同步序列长度(位) const int sample_per_bit = static_cast<int>(SAMPLES_PER_BIT); //if (search_range <= 0) return -1; // 创建理想帧头波形 QVector<float> sync_pattern(sync_length * sample_per_bit); int bit = 1; for (int i = 0; i < sync_length; i++) { bit = (i % 2 == 0) ? bit : !bit; // 交替10 for (int j = 0; j < sample_per_bit; j++) { sync_pattern[i * sample_per_bit + j] = bit ? 1.0f : -1.0f; } } // 使用互相关搜索 int best_offset = -1; float max_correlation = -FLT_MAX; const int search_range = length - sync_pattern.size(); for (int offset = 0; offset < search_range; offset += sample_per_bit / 4) { float signal_energy = 0.0f; float corr = 0.0f; for (int i = 0; i < sync_pattern.size(); i++) { corr += data[offset + i] * sync_pattern[i]; signal_energy += data[offset + i] * data[offset + i]; } // 归一化互相关 float norm_corr = corr / sqrt(signal_energy * sync_pattern.size()); if (norm_corr > max_correlation) { max_correlation = norm_corr; best_offset = offset; } } // 阈值判定(经验值) // float threshold = header_waveform.size() * 0.6f; return (max_correlation > 0.6f) ? best_offset : -1; } // ================= 汉明编解码 ================= void encode_hamming(uint8_t* data, int& bit_count) { int original_count = bit_count; int hamming_count = (original_count * 7) / 4; if (hamming_count > MAX_BITS) hamming_count = MAX_BITS; uint8_t* hamming_data = new uint8_t[MAX_BITS](); int h_index = 0; for (int i = 0; i < original_count; i += 4) { if (h_index + 7 >= MAX_BITS) break; if (i + 3 >= original_count) break; uint8_t d1 = data[i]; uint8_t d2 = data[i + 1]; uint8_t d3 = data[i + 2]; uint8_t d4 = data[i + 3]; // 确保数据位是0或1 // d1 = data[i] ? 1 : 0; // d2 = data[i + 1] ? 1 : 0; // d3 = data[i + 2] ? 1 : 0; // d4 = data[i + 3] ? 1 : 0; // 计算校验位 uint8_t p1 = d1 ^ d2 ^ d4; uint8_t p2 = d1 ^ d3 ^ d4; uint8_t p3 = d2 ^ d3 ^ d4; hamming_data[h_index++] = p1; hamming_data[h_index++] = p2; hamming_data[h_index++] = d1; hamming_data[h_index++] = p3; hamming_data[h_index++] = d2; hamming_data[h_index++] = d3; hamming_data[h_index++] = d4; } // 复制回原数组 memcpy(data, hamming_data, h_index); bit_count = h_index; delete[] hamming_data; } int decode_hamming(uint8_t* data, int bit_count) { int decoded_count = 0; uint8_t* decoded_data = new uint8_t[MAX_BITS](); for (int i = 0; i < bit_count; i += 7) { if (i + 6 >= bit_count) break; if (decoded_count + 4 >= MAX_BITS) break; uint8_t p1 = data[i]; uint8_t p2 = data[i + 1]; uint8_t d1 = data[i + 2]; uint8_t p3 = data[i + 3]; uint8_t d2 = data[i + 4]; uint8_t d3 = data[i + 5]; uint8_t d4 = data[i + 6]; // 确保数据位是0或1 /* p1 = data[i] ? 1 : 0; p2 = data[i + 1] ? 1 : 0; d1 = data[i + 2] ? 1 : 0; p3 = data[i + 3] ? 1 : 0; d2 = data[i + 4] ? 1 : 0; d3 = data[i + 5] ? 1 : 0; d4 = data[i + 6] ? 1 : 0;*/ // 计算校验子 uint8_t s1 = p1 ^ d1 ^ d2 ^ d4; uint8_t s2 = p2 ^ d1 ^ d3 ^ d4; uint8_t s3 = p3 ^ d2 ^ d3 ^ d4; int error_pos = s1 + (s2 << 1) + (s3 << 2); // 错误纠正 if (error_pos > 0) { switch (error_pos) { case 1: p1 ^= 1; break; case 2: p2 ^= 1; break; case 3: d1 ^= 1; break; case 4: p3 ^= 1; break; case 5: d2 ^= 1; break; case 6: d3 ^= 1; break; case 7: d4 ^= 1; break; } } // 提取数据位 decoded_data[decoded_count++] = d1; decoded_data[decoded_count++] = d2; decoded_data[decoded_count++] = d3; decoded_data[decoded_count++] = d4; } // 复制回原数组 memcpy(data, decoded_data, decoded_count); delete[] decoded_data; return decoded_count; } // ================= 文件操作 ================= void save_signal_to_file(const char* filename, const float* data, int count, int sample_rate, SignalType sig_type, ModulationType mod_type) { QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "无法打开文件进行写入:" << filename; return; } QDataStream out(&file); out.setVersion(QDataStream::Qt_5_9); // 写入文件头 out << static_cast<qint32>(sample_rate); out << static_cast<qint32>(sig_type); out << static_cast<qint32>(mod_type); out << static_cast<qint32>(count); // 写入数据 for (int i = 0; i < count; i++) { out << static_cast<float>(data[i]); } file.close(); } void load_signal_from_file(const char* filename, float* data, int* count, int* sample_rate, SignalType* sig_type, ModulationType* mod_type) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "无法打开文件进行读取:" << filename; return; } QDataStream in(&file); in.setVersion(QDataStream::Qt_5_9); // 读取文件头 qint32 sr, st, mt, c; in >> sr; in >> st; in >> mt; in >> c; *sample_rate = sr; *sig_type = static_cast<SignalType>(st); *mod_type = static_cast<ModulationType>(mt); *count = (c > MAX_SAMPLES) ? MAX_SAMPLES : c; // 读取数据 for (int i = 0; i < *count; i++) { float value; in >> value; data[i] = value; } file.close(); } // ================= 噪声处理 ================= void apply_noise(float* signal, int count, float snr_db) { // 计算信号功率 float signal_power = 0.0f; for (int i = 0; i < count; i++) { signal_power += signal[i] * signal[i]; } signal_power /= static_cast<float>(count); // 计算噪声功率 float snr_linear = powf(10.0f, snr_db / 10.0f); float noise_power = signal_power / snr_linear; float noise_stddev = sqrtf(noise_power); // 生成高斯噪声 std::random_device rd; std::mt19937 gen(rd()); std::normal_distribution<float> dist(0.0f, noise_stddev); // 添加噪声 for (int i = 0; i < count; i++) { signal[i] += dist(gen); } } // ================= GUI实现 ================= CommunicationSystemGUI::CommunicationSystemGUI(QWidget* parent) : QMainWindow(parent), sourceWaveformView(new QChartView(this)), sourceSpectrumView(new QChartView(this)), modulatedWaveformView(new QChartView(this)), modulatedSpectrumView(new QChartView(this)), receivedWaveformView(new QChartView(this)), receivedSpectrumView(new QChartView(this)), demodulatedWaveformView(new QChartView(this)), audioInput(nullptr), audioOutput(nullptr) { // 初始化窗口 setWindowTitle("数字通信系统"); resize(1200, 800); // 创建主布局 QWidget* centralWidget = new QWidget(this); QVBoxLayout* mainLayout = new QVBoxLayout(centralWidget); setCentralWidget(centralWidget); // 创建分割器 QSplitter* mainSplitter = new QSplitter(Qt::Vertical, centralWidget); mainLayout->addWidget(mainSplitter); // 创建控制面板 QWidget* controlWidget = new QWidget(); QHBoxLayout* controlLayout = new QHBoxLayout(controlWidget); // 创建发送方控制组 createTransmitterGroup(); controlLayout->addWidget(transmitterGroup); // 创建接收方控制组 createReceiverGroup(); controlLayout->addWidget(receiverGroup); mainSplitter->addWidget(controlWidget); // 创建波形显示区域 createWaveformDisplays(); mainSplitter->addWidget(waveformFrame); // 设置分割器比例 mainSplitter->setSizes({ 200, 600 }); // 创建状态栏 statusBar = new QStatusBar(); setStatusBar(statusBar); // 创建状态栏组件 transmitStatusLabel = new QLabel("发送状态: 空闲"); receiveStatusLabel = new QLabel("接收状态: 空闲"); transmitProgressBar = new QProgressBar(); transmitProgressBar->setRange(0, 100); transmitProgressBar->setFixedWidth(150); transmitProgressBar->setTextVisible(true); receiveProgressBar = new QProgressBar(); receiveProgressBar->setRange(0, 100); receiveProgressBar->setFixedWidth(150); receiveProgressBar->setTextVisible(true); // 添加到状态栏 statusBar->addPermanentWidget(transmitStatusLabel); statusBar->addPermanentWidget(transmitProgressBar); statusBar->addPermanentWidget(receiveStatusLabel); statusBar->addPermanentWidget(receiveProgressBar); // 确保接收器内存分配正确 receiver.received = new float[MAX_SAMPLES](); receiver.demodulated = new float[MAX_SAMPLES](); // 初始化通信模块 init_transmitter(&transmitter, SINE_WAVE, BPSK, CARRIER_FREQ, SAMPLE_RATE); init_receiver(&receiver, CARRIER_FREQ, SAMPLE_RATE); // 初始化音频设备 initAudio(); } CommunicationSystemGUI::~CommunicationSystemGUI() { delete[] transmitter.samples; delete[] transmitter.modulated; delete[] receiver.received; delete[] receiver.demodulated; delete audioInput; delete audioOutput; } void CommunicationSystemGUI::createTransmitterGroup() { transmitterGroup = new QGroupBox("发送方"); QGridLayout* layout = new QGridLayout(transmitterGroup); // 信号类型 layout->addWidget(new QLabel("信号类型:"), 0, 0); signalTypeCombo = new QComboBox(); signalTypeCombo->addItem("正弦波", SINE_WAVE); signalTypeCombo->addItem("方波", SQUARE_WAVE); signalTypeCombo->addItem("锯齿波", SAWTOOTH_WAVE); signalTypeCombo->addItem("随机数据", RANDOM_DATA); signalTypeCombo->addItem("文本数据", TEXT_DATA); signalTypeCombo->addItem("文件数据", FILE_DATA); layout->addWidget(signalTypeCombo, 0, 1); // 调制类型 layout->addWidget(new QLabel("调制类型:"), 1, 0); modulationTypeCombo = new QComboBox(); modulationTypeCombo->addItem("BPSK", BPSK); modulationTypeCombo->addItem("QPSK", QPSK); modulationTypeCombo->addItem("FSK", FSK); modulationTypeCombo->addItem("AM", AM); layout->addWidget(modulationTypeCombo, 1, 1); // 载波频率 layout->addWidget(new QLabel("载波频率(Hz):"), 2, 0); carrierFreqEdit = new QLineEdit(QString::number(CARRIER_FREQ)); carrierFreqEdit->setValidator(new QDoubleValidator(100, 10000, 2, this)); layout->addWidget(carrierFreqEdit, 2, 1); // 文本输入 layout->addWidget(new QLabel("文本数据:"), 3, 0, 1, 2); textDataEdit = new QTextEdit(); textDataEdit->setMaximumHeight(60); layout->addWidget(textDataEdit, 4, 0, 1, 2); // 按钮 setTextButton = new QPushButton("设置文本"); loadFileButton = new QPushButton("加载文件"); generateButton = new QPushButton("生成信号"); modulateButton = new QPushButton("调制"); transmitButton = new QPushButton("传输"); saveSignalButton = new QPushButton("保存信号"); layout->addWidget(setTextButton, 5, 0); layout->addWidget(loadFileButton, 5, 1); layout->addWidget(generateButton, 6, 0); layout->addWidget(modulateButton, 6, 1); layout->addWidget(transmitButton, 7, 0); layout->addWidget(saveSignalButton, 7, 1); // 连接信号槽 connect(signalTypeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CommunicationSystemGUI::onSignalTypeChanged); connect(setTextButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onSetTextData); connect(loadFileButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onLoadFileData); connect(generateButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onGenerateSignal); connect(modulateButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onModulateSignal); connect(transmitButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onTransmitSignal); connect(saveSignalButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onSaveSignal); } void CommunicationSystemGUI::createReceiverGroup() { receiverGroup = new QGroupBox("接收方"); QGridLayout* layout = new QGridLayout(receiverGroup); // 按钮 loadSignalButton = new QPushButton("加载信号"); receiveButton = new QPushButton("接收"); demodulateButton = new QPushButton("解调"); decodeButton = new QPushButton("解码"); saveReceivedButton = new QPushButton("保存接收"); saveDecodedButton = new QPushButton("保存解码"); layout->addWidget(loadSignalButton, 0, 0); layout->addWidget(receiveButton, 0, 1); layout->addWidget(demodulateButton, 1, 0); layout->addWidget(decodeButton, 1, 1); layout->addWidget(saveReceivedButton, 2, 0); layout->addWidget(saveDecodedButton, 2, 1); // 解码文本显示 decodedTextEdit = new QTextEdit(); decodedTextEdit->setReadOnly(true); decodedTextEdit->setMaximumHeight(80); layout->addWidget(new QLabel("解码文本:"), 3, 0, 1, 2); layout->addWidget(decodedTextEdit, 4, 0, 1, 2); // 连接信号槽 connect(loadSignalButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onLoadSignal); connect(receiveButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onReceiveSignal); connect(demodulateButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onDemodulateSignal); connect(decodeButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onDecodeSignal); connect(saveReceivedButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onSaveReceived); connect(saveDecodedButton, &QPushButton::clicked, this, &CommunicationSystemGUI::onSaveDecoded); } void CommunicationSystemGUI::createWaveformDisplays() { waveformFrame = new QFrame(); QGridLayout* gridLayout = new QGridLayout(waveformFrame); // 第一行:源信号波形(左)和调制信号波形(右) sourceWaveformView = new QChartView(); modulatedWaveformView = new QChartView(); gridLayout->addWidget(new QLabel("源信号波形"), 0, 0); gridLayout->addWidget(sourceWaveformView, 1, 0); gridLayout->addWidget(new QLabel("调制信号波形"), 0, 1); gridLayout->addWidget(modulatedWaveformView, 1, 1); // 第二行:接收信号波形(左)和解调信号波形(右) receivedWaveformView = new QChartView(); demodulatedWaveformView = new QChartView(); gridLayout->addWidget(new QLabel("接收信号波形"), 2, 0); gridLayout->addWidget(receivedWaveformView, 3, 0); gridLayout->addWidget(new QLabel("解调信号波形"), 2, 1); gridLayout->addWidget(demodulatedWaveformView, 3, 1); // 设置行和列的比例 gridLayout->setRowStretch(1, 1); gridLayout->setRowStretch(3, 1); gridLayout->setColumnStretch(0, 1); gridLayout->setColumnStretch(1, 1); } void CommunicationSystemGUI::initAudio() { QAudioFormat format; format.setSampleRate(SAMPLE_RATE); format.setChannelCount(1); format.setSampleSize(32); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::Float); // 输入设备 QAudioDeviceInfo inputDevice = QAudioDeviceInfo::defaultInputDevice(); if (!inputDevice.isFormatSupported(format)) { qWarning() << "输入设备不支持请求的格式,使用最接近的匹配"; format = inputDevice.nearestFormat(format); } if (!inputDevice.isFormatSupported(format)) { qWarning() << "默认格式不支持,使用最接近的格式"; format = inputDevice.nearestFormat(format); } audioInput = new QAudioInput(inputDevice, format, this); // 输出设备 QAudioDeviceInfo outputDevice = QAudioDeviceInfo::defaultOutputDevice(); if (outputDevice.isNull()) { qWarning() << "未找到音频输出设备"; return; } if (!outputDevice.isFormatSupported(format)) { qWarning() << "默认格式不支持,使用最接近的格式"; format = outputDevice.nearestFormat(format); } audioOutput = new QAudioOutput(outputDevice, format, this); } // ================= 核心功能实现 ================= void CommunicationSystemGUI::onGenerateSignal() { transmitter.signal_type = static_cast<SignalType>(signalTypeCombo->currentData().toInt()); transmitter.mod_type = static_cast<ModulationType>(modulationTypeCombo->currentData().toInt()); transmitter.carrier_freq = carrierFreqEdit->text().toFloat(); if (transmitter.signal_type == TEXT_DATA) { onSetTextData(); } else if (transmitter.signal_type == FILE_DATA) { onLoadFileData(); } else { generate_signal(&transmitter, 1000); // 1秒信号 plotSignal(sourceWaveformView, transmitter.samples, transmitter.sample_count, "源信号波形"); } showStatusMessage("信号生成完成"); } void CommunicationSystemGUI::onModulateSignal() { if (transmitter.sample_count == 0) { QMessageBox::warning(this, "错误", "请先生成信号"); return; } modulate_signal(&transmitter); plotSignal(modulatedWaveformView, transmitter.modulated, transmitter.sample_count, "调制信号波形"); showStatusMessage("信号调制完成"); } void CommunicationSystemGUI::onSetTextData() { QString text = textDataEdit->toPlainText(); if (text.isEmpty()) { QMessageBox::warning(this, "错误", "请输入文本"); return; } // 使用QString确保正确处理中文 QByteArray utf8Data = text.toUtf8(); set_text_data(&transmitter, utf8Data.constData()); set_text_data(&transmitter, text.toUtf8().constData()); plotSignal(sourceWaveformView, transmitter.samples, transmitter.sample_count, "文本信号波形"); showStatusMessage("文本数据已设置"); } void CommunicationSystemGUI::onLoadFileData() { QString filename = QFileDialog::getOpenFileName(this, "打开数据文件"); if (!filename.isEmpty()) { load_file_data(&transmitter, filename.toUtf8().constData()); plotSignal(sourceWaveformView, transmitter.samples, transmitter.sample_count, "文件数据波形"); showStatusMessage("文件数据已加载"); } } void CommunicationSystemGUI::onDemodulateSignal() { if (receiver.sample_count == 0) { QMessageBox::warning(this, "错误", "没有可解调的信号"); return; } // 传递发送方的信号类型和调制类型 receiver.signal_type = transmitter.signal_type; receiver.mod_type = transmitter.mod_type; demodulate_signal(&receiver); plotSignal(demodulatedWaveformView, receiver.demodulated, receiver.sample_count, "解调信号波形"); // 如果是文本信号,自动解码 if (receiver.signal_type == TEXT_DATA) { onDecodeSignal(); } showStatusMessage("信号解调完成"); } void CommunicationSystemGUI::onDecodeSignal() { if (receiver.sample_count == 0) { QMessageBox::warning(this, "错误", "没有可解码的信号"); return; } decode_signal(&receiver); if (strlen(receiver.decoded_text) > 0) { decodedTextEdit->setPlainText(receiver.decoded_text); showStatusMessage("解码成功"); } else { decodedTextEdit->setPlainText("解码失败:请检查调制设置和信号质量"); showStatusMessage("解码失败"); } } void CommunicationSystemGUI::onSaveSignal() { if (transmitter.sample_count == 0) { QMessageBox::warning(this, "错误", "没有可保存的信号"); return; } QString filename = QFileDialog::getSaveFileName(this, "保存信号", "", "信号文件 (*.sig)"); if (!filename.isEmpty()) { save_signal_to_file(filename.toUtf8().constData(), transmitter.modulated, transmitter.sample_count, transmitter.sample_rate, transmitter.signal_type, transmitter.mod_type); showStatusMessage("信号已保存: " + filename); } } void CommunicationSystemGUI::onLoadSignal() { QString filename = QFileDialog::getOpenFileName(this, "加载信号", "", "信号文件 (*.sig)"); if (!filename.isEmpty()) { load_signal_from_file(filename.toUtf8().constData(), receiver.received, &receiver.sample_count, &receiver.sample_rate, &receiver.signal_type, &receiver.mod_type); plotSignal(receivedWaveformView, receiver.received, receiver.sample_count, "接收信号波形"); showStatusMessage("信号已加载: " + filename); } } void CommunicationSystemGUI::onSaveReceived() { if (receiver.sample_count == 0) { QMessageBox::warning(this, "错误", "没有可保存的接收信号"); return; } QString filename = QFileDialog::getSaveFileName(this, "保存接收信号", "", "信号文件 (*.sig)"); if (!filename.isEmpty()) { save_signal_to_file(filename.toUtf8().constData(), receiver.received, receiver.sample_count, receiver.sample_rate, receiver.signal_type, receiver.mod_type); showStatusMessage("接收信号已保存: " + filename); } } void CommunicationSystemGUI::onSaveDecoded() { if (receiver.signal_type == TEXT_DATA) { QString filename = QFileDialog::getSaveFileName(this, "保存解码文本", "", "文本文件 (*.txt)"); if (!filename.isEmpty()) { QFile file(filename); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); out << receiver.decoded_text; file.close(); showStatusMessage("解码文本已保存: " + filename); } } } else { QString filename = QFileDialog::getSaveFileName(this, "保存解码信号", "", "数据文件 (*.dat)"); if (!filename.isEmpty()) { QFile file(filename); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); for (int i = 0; i < receiver.sample_count; i++) { out << receiver.demodulated[i] << "\n"; } file.close(); showStatusMessage("解码信号已保存: " + filename); } } } } void CommunicationSystemGUI::onSignalTypeChanged(int index) { SignalType type = static_cast<SignalType>(signalTypeCombo->itemData(index).toInt()); textDataEdit->setEnabled(type == TEXT_DATA); setTextButton->setEnabled(type == TEXT_DATA); loadFileButton->setEnabled(type == FILE_DATA); } void CommunicationSystemGUI::onTransmitSignal() { if (audioOutput && transmitter.sample_count > 0) { // 更新状态 transmitStatusLabel->setText("发送状态: 传输中"); transmitProgressBar->setValue(0); // 准备输出缓冲区 QBuffer* outputBuffer = new QBuffer(this); outputBuffer->open(QIODevice::ReadWrite); // 计算传输时间(毫秒) float duration = (transmitter.sample_count * 1000.0f) / transmitter.sample_rate; outputBuffer->write(reinterpret_cast<const char*>(transmitter.modulated), transmitter.sample_count * sizeof(float)); outputBuffer->seek(0); // 开始播放 audioOutput->start(outputBuffer); showStatusMessage("开始传输信号..."); // 启动传输计时器 transmitTimer.start(); // 设置进度更新定时器 QTimer* progressTimer = new QTimer(this); connect(progressTimer, &QTimer::timeout, [=]() { int elapsed = transmitTimer.elapsed(); int progress = qMin(100, static_cast<int>((elapsed / duration) * 100)); transmitProgressBar->setValue(progress); if (progress >= 100) { progressTimer->stop(); progressTimer->deleteLater(); transmitStatusLabel->setText("发送状态: 完成"); showStatusMessage("信号传输完成"); } }); progressTimer->start(100); // 每100ms更新一次 } else { QMessageBox::warning(this, "错误", "音频输出设备未初始化或没有可传输的信号"); } } void CommunicationSystemGUI::onReceiveSignal() { if (audioInput) { // 更新状态 receiveStatusLabel->setText("接收状态: 接收中"); receiveProgressBar->setValue(0); // 准备输入缓冲区 inputBuffer.setData(QByteArray()); inputBuffer.open(QIODevice::WriteOnly); // 开始录音 audioInput->start(&inputBuffer); showStatusMessage("开始接收信号..."); // 设置进度更新定时器 QTimer* progressTimer = new QTimer(this); connect(progressTimer, &QTimer::timeout, [=]() { int elapsed = receiveTimer.elapsed(); int progress = qMin(100, static_cast<int>((elapsed / 5000.0) * 100)); receiveProgressBar->setValue(progress); if (progress >= 100) { progressTimer->stop(); progressTimer->deleteLater(); receiveStatusLabel->setText("接收状态: 完成"); // === 新增:处理接收到的音频数据 === inputBuffer.close(); QByteArray audioData = inputBuffer.data(); if (!audioData.isEmpty()) { // 转换为浮点数组 const float* rawData = reinterpret_cast<const float*>(audioData.constData()); int numSamples = audioData.size() / sizeof(float); // 设置接收器的信号类型和调制类型 receiver.signal_type = transmitter.signal_type; receiver.mod_type = transmitter.mod_type; receiver.sample_rate = transmitter.sample_rate; // 存储到接收器 receiver.sample_count = numSamples; for (int i = 0; i < receiver.sample_count; i++) { receiver.received[i] = rawData[i]; } // 绘制接收信号 plotSignal(receivedWaveformView, receiver.received, receiver.sample_count, "接收信号波形"); showStatusMessage(QString("接收完成,共 %1 个样本").arg(receiver.sample_count)); onDemodulateSignal(); } else { showStatusMessage("未接收到任何数据"); } } }); progressTimer->start(100); receiveTimer.start(); } else { QMessageBox::warning(this, "错误", "音频输入设备未初始化"); } } // 添加处理接收到的音频数据的函数 void CommunicationSystemGUI::processReceivedAudio() { inputBuffer.close(); if (audioData.size() == 0) { showStatusMessage("未接收到任何数据"); return; } // 将接收到的数据转换为浮点数组 const float* rawData = reinterpret_cast<const float*>(audioData.constData()); int numSamples = audioData.size() / sizeof(float); if (numSamples > MAX_SAMPLES) { numSamples = MAX_SAMPLES; showStatusMessage("警告:接收数据超出最大样本数,已截断"); } // 复制到接收器 receiver.sample_count = numSamples; receiver.signal_type = transmitter.signal_type; // 设置信号类型 receiver.mod_type = transmitter.mod_type; // 设置调制类型 for (int i = 0; i < numSamples; i++) { receiver.received[i] = rawData[i]; } // 绘制接收信号 plotSignal(receivedWaveformView, receiver.received, receiver.sample_count, "接收信号波形"); showStatusMessage(QString("接收完成,共 %1 个样本").arg(numSamples)); // 清空数据为下次接收准备 audioData.clear(); } // ================= 辅助函数 ================= void CommunicationSystemGUI::plotSignal(QChartView* chartView, float* data, int count, const QString& title) { // 严格检查输入参数 if (!chartView || !data || count <= 0) { qWarning() << "无效的绘图参数"; return; } // 创建新的series QLineSeries* series = new QLineSeries(); // 计算步长以减少点数 int step = std::max(1, count / 1000); if (step < 1) step = 1; // 添加数据点 for (int i = 0; i < count; i += step) { series->append(i, data[i]); } // 创建新图表 QChart* chart = new QChart(); chart->addSeries(series); chart->setTitle(title); // 创建并设置X轴 QValueAxis* axisX = new QValueAxis(); axisX->setTitleText("采样点"); axisX->setRange(0, count); chart->addAxis(axisX, Qt::AlignBottom); series->attachAxis(axisX); // 创建并设置Y轴 QValueAxis* axisY = new QValueAxis(); axisY->setTitleText("幅度"); // 自动计算Y轴范围 auto minmax = std::minmax_element(data, data + count); float minVal = *minmax.first; float maxVal = *minmax.second; // 确保有合理的范围 if (fabs(maxVal - minVal) < 0.001f) { minVal -= 1.0f; maxVal += 1.0f; } else { // 扩展10%的范围 float range = maxVal - minVal; minVal -= range * 0.1f; maxVal += range * 0.1f; } axisY->setRange(minVal, maxVal); chart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisY); // 设置图表到视图 chartView->setChart(chart); chartView->setRenderHint(QPainter::Antialiasing); } void CommunicationSystemGUI::showStatusMessage(const QString& message) { statusBar->showMessage(message, 5000); // 显示5秒 } 还是没有解决这个程序我说的问题。就两个要求:1.输入输出波形相同;2.输入文本数据与输出解码相同。实现数字通信
06-10
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值