#!/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_())