M&M error plot

This commit is contained in:
2025-10-26 11:51:49 +01:00
parent 63f706eee5
commit ae04d5e2bd
5 changed files with 833 additions and 54 deletions

View File

@ -5,91 +5,137 @@ import numpy as np
import sys, os
# -------------------- Fichiers de données --------------------
REF_FILE = "constellation_ref.dat"
RX_FILE = "constellation.dat"
ERROR_FILE = "pll_error.dat"
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):
return np.loadtxt(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):
return np.loadtxt(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="Constellation")
win.resize(900, 900)
win = pg.GraphicsLayoutWidget(show=True, title="Synchronisation QAM (M&M)")
win.resize(1200, 800)
win.setBackground('#0a0a0a') # fond noir profond
plot = win.addPlot()
plot.setAspectLocked(True) # axes égaux
# -------------------- 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))
# -------------------- Axes stylés --------------------
plot.getAxis('left').setPen(pg.mkPen('#AAA', width=2))
plot.getAxis('bottom').setPen(pg.mkPen('#AAA', width=2))
plot.getAxis('left').setTextPen(pg.mkPen('#EEE'))
plot.getAxis('bottom').setTextPen(pg.mkPen('#EEE'))
plot.setLabel('left', 'Quadrature (Q)', color='#EEE', size='12pt')
plot.setLabel('bottom', 'In-phase (I)', color='#EEE', size='12pt')
# -------------------- Grille subtile --------------------
grid = pg.GridItem()
grid.setPen(pg.mkPen('#444', width=1, style=QtCore.Qt.DotLine))
plot.addItem(grid)
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)
error_data = load_error(ERROR_FILE)
# Points référence
ref_plot = plot.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')
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.plot(rx_data[:,0], rx_data[:,1],
pen=None,
symbol='x',
symbolSize=6,
symbolBrush=pg.mkBrush('#FF4500'), # orange vif
symbolPen=None,
name='Reçu')
# PLL error en vert
error_plot = plot.plot(error_data[:,0], error_data[:,1],
pen=pg.mkPen('#00FF00', width=2),
symbol=None,
name='PLL error')
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 = plot.addLegend()
for item in legend.items:
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)
error_data = load_error(ERROR_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])
if error_data.size > 0:
error_plot.setData(error_data[:,0], error_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)

BIN
QAM/out

Binary file not shown.

View File

@ -364,7 +364,7 @@ void pll(qam_system* qam, double complex* s_with_preamble, double complex* r_cor
}
// Synchro temporelle Müller et Müller
void muller_muller_sync(qam_system* qam, double complex* s_data, int nb_symbols, double Kp_mm, double Ki_mm, double complex* r_mm_out) {
void muller_muller_sync(qam_system* qam, double complex* s_data, int nb_symbols, double Kp_mm, double Ki_mm, double complex* r_mm_out, FILE* fp_error) {
double integrator = 0.0;
double timing_offset = 0.0;
double current_idx_double = 0.0;
@ -411,6 +411,11 @@ void muller_muller_sync(qam_system* qam, double complex* s_data, int nb_symbols,
current_idx_double += qam->N - timing_offset;
d_k_minus_1 = d_k;
if (fp_error) {
fprintf(fp_error, "%d % .8f\n", k, 5 * timing_offset);
fflush(fp_error);
}
}
}
@ -474,7 +479,7 @@ int main () {
init_constellation(&qam);
// Conversion du texte en bits
char* texte = "Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux";
char* texte = "Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux,Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux,Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux,Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux,Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux, Vif juge, trempez ce blond whisky aqueux,FIN";
int nb_chars = strlen(texte);
int nb_bits = nb_chars * 8;
int nb_symbols = (nb_bits + qam.k - 1) / qam.k;
@ -530,12 +535,14 @@ int main () {
fclose(fp_pll_error);
double complex* s_bande_base = malloc(sizeof(double complex) * nb_symbols * qam.N);
demodulate_carrier(&qam, s_corrected + L * qam.N, s_bande_base, total_samples);
demodulate_carrier(&qam, s_corrected + L * qam.N, s_bande_base, nb_symbols * qam.N);
double complex* r_mm_out = malloc(sizeof(double complex) * nb_bits);
double Kp_mm = 0.05;
double Ki_mm = 0.001;
muller_muller_sync(&qam, s_bande_base, nb_symbols, Kp_mm, Ki_mm, r_mm_out);
double complex* r_mm_out = malloc(sizeof(double complex) * nb_symbols);
double Kp_mm = 0.5;
double Ki_mm = 0.01;
FILE *fp_mm_error = fopen("mm_timing_error.dat", "w");
muller_muller_sync(&qam, s_bande_base, nb_symbols, Kp_mm, Ki_mm, r_mm_out, fp_mm_error);
fclose(fp_mm_error);
// Démodulation
FILE *fp_constel = fopen("constellation.dat", "w");