Files
DSP/QAM/debug.py
2025-10-26 11:51:49 +01:00

151 lines
5.9 KiB
Python

#!/usr/bin/env python3
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets, QtCore
import numpy as np
import sys, os
# -------------------- Fichiers de données --------------------
REF_FILE = "constellation_ref.dat"
RX_FILE = "constellation.dat"
PLL_ERROR_FILE = "pll_error.dat"
MM_ERROR_FILE = "mm_timing_error.dat" # Nouveau fichier pour l'erreur M&M
# -------------------- Fonctions de chargement --------------------
def load_points(filename):
if os.path.exists(filename):
# Utiliser usecols=(0, 1) pour être sûr d'avoir 2 colonnes
return np.loadtxt(filename, usecols=(0, 1))
else:
return np.zeros((0,2))
def load_error(filename):
if os.path.exists(filename):
# Colonne 0: Indice du symbole, Colonne 1: Valeur d'erreur
return np.loadtxt(filename, usecols=(0, 1))
else:
return np.zeros((0,2))
# -------------------- Application --------------------
app = QtWidgets.QApplication(sys.argv)
win = pg.GraphicsLayoutWidget(show=True, title="Synchronisation QAM (M&M)")
win.resize(1200, 800)
win.setBackground('#0a0a0a') # fond noir profond
# -------------------- Plot 1: Constellation --------------------
# Le premier plot prend la première ligne complète
plot_constel = win.addPlot(title="Constellation Corrigée (Après M&M)")
plot_constel.setAspectLocked(True) # axes égaux
plot_constel.setLabel('left', 'Quadrature (Q)', color='#EEE')
plot_constel.setLabel('bottom', 'In-phase (I)', color='#EEE')
plot_constel.getAxis('left').setPen(pg.mkPen('#AAA', width=2))
plot_constel.getAxis('bottom').setPen(pg.mkPen('#AAA', width=2))
grid_constel = pg.GridItem()
grid_constel.setPen(pg.mkPen('#444', width=1, style=QtCore.Qt.DotLine))
plot_constel.addItem(grid_constel)
# -------------------- Chargement initial --------------------
ref_data = load_points(REF_FILE)
rx_data = load_points(RX_FILE)
# Points référence
ref_plot = plot_constel.plot(ref_data[:,0], ref_data[:,1],
pen=None,
symbol='o',
symbolSize=10,
symbolBrush=pg.mkBrush('#1E90FF'), # bleu vif
symbolPen=None,
name='Référence')
# Points reçus
rx_plot = plot_constel.plot(rx_data[:,0], rx_data[:,1],
pen=None,
symbol='x',
symbolSize=6,
symbolBrush=pg.mkBrush('#FF4500'), # orange vif
symbolPen=None,
name='Reçu')
# -------------------- Légende stylée --------------------
legend_constel = plot_constel.addLegend()
for item in legend_constel.items:
item[1].setPen(pg.mkPen('#FFF', width=2))
# -------------------- Plot 2: Erreur de Phase PLL --------------------
win.nextRow() # On passe à la deuxième ligne
plot_pll = win.addPlot(title="Erreur de Phase PLL (Boucle de Costas)")
plot_pll.setLabel('left', 'Erreur de Phase Instant. (%)', color='#EEE')
plot_pll.setLabel('bottom', 'Symbole k', color='#EEE')
plot_pll.getAxis('left').setPen(pg.mkPen('#AAA', width=2))
plot_pll.getAxis('bottom').setPen(pg.mkPen('#AAA', width=2))
pll_error_data = load_error(PLL_ERROR_FILE)
pll_error_plot = plot_pll.plot(pll_error_data[:,0], pll_error_data[:,1],
pen=pg.mkPen('#00FF00', width=2), # Vert
symbol=None,
name='Erreur PLL')
# -------------------- Plot 3: Correction Temporelle M&M --------------------
# Ce plot est ajouté SANS win.nextRow() pour le placer à droite du Plot 2 (horizontalement)
plot_mm = win.addPlot(title="Correction Temporelle M&M (Sortie NCO)")
plot_mm.setLabel('left', 'Timing Offset (échantillons)', color='#EEE')
plot_mm.setLabel('bottom', 'Symbole k', color='#EEE')
plot_mm.getAxis('left').setPen(pg.mkPen('#AAA', width=2))
plot_mm.getAxis('bottom').setPen(pg.mkPen('#AAA', width=2))
mm_error_data = load_error(MM_ERROR_FILE)
mm_error_plot = plot_mm.plot(mm_error_data[:,0], mm_error_data[:,1],
pen=pg.mkPen('#FFFF00', width=2), # Jaune
symbol=None,
name='Correction M&M')
# Ligne zéro pour la référence de convergence
plot_mm.addLine(y=0, pen=pg.mkPen('#555', width=1, style=QtCore.Qt.DashLine))
# Ligne de l'offset initial (N/4) pour référence
N_val = 0
if 'qam' in dir():
N_val = qam.N
plot_mm.addLine(y=N_val/4, pen=pg.mkPen('#555', width=1, style=QtCore.Qt.DashLine))
# -------------------- Timer pour mise à jour dynamique --------------------
def update():
global N_val
ref_data = load_points(REF_FILE)
rx_data = load_points(RX_FILE)
pll_error_data = load_error(PLL_ERROR_FILE)
mm_error_data = load_error(MM_ERROR_FILE)
# Constellation
if ref_data.size > 0:
ref_plot.setData(ref_data[:,0], ref_data[:,1])
if rx_data.size > 0:
rx_plot.setData(rx_data[:,0], rx_data[:,1])
# PLL Error
if pll_error_data.size > 0:
pll_error_plot.setData(pll_error_data[:,0], pll_error_data[:,1])
# M&M Timing Correction
if mm_error_data.size > 0:
mm_error_plot.setData(mm_error_data[:,0], mm_error_data[:,1])
# Mise à jour de la ligne de référence N/4 si N est connu
if N_val == 0 and len(mm_error_data) > 0:
# N = Fs * Ts = 44100 * 0.01 = 441. Utiliser cette valeur si non récupérable
N_val = 441 # Valeur par défaut basée sur main.c
plot_mm.clear()
plot_mm.addItem(mm_error_plot)
plot_mm.addLine(y=0, pen=pg.mkPen('#555', width=1, style=QtCore.Qt.DashLine))
plot_mm.addLine(y=N_val/4.0, pen=pg.mkPen('#555', width=1, style=QtCore.Qt.DotLine, dash=[2, 2])) # Ligne de l'offset initial
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(500) # ms
# -------------------- Anti-aliasing --------------------
pg.setConfigOptions(antialias=True)
# -------------------- Exécution --------------------
if __name__ == '__main__':
sys.exit(app.exec_())