Compare commits

...

21 Commits

Author SHA1 Message Date
367eeb5847 RRC impulsion response 2025-10-26 12:38:12 +01:00
ae04d5e2bd M&M error plot 2025-10-26 11:51:49 +01:00
63f706eee5 TODO update 2025-10-24 12:33:24 +02:00
d5e0e4129b Delete QAM/pll_error.dat 2025-10-24 12:32:16 +02:00
132a8f903f Delete QAM/pll_error1.dat 2025-10-24 12:32:12 +02:00
3b583de596 Delete QAM/constellation.dat 2025-10-24 12:32:09 +02:00
144cbbc5c3 Delete QAM/constellation_ref.dat 2025-10-24 12:32:05 +02:00
e591d98772 Delete QAM/baseband_signal.dat 2025-10-24 12:31:58 +02:00
526327f313 add .dat to gitignore 2025-10-24 12:31:22 +02:00
1b3e861181 add .dat to gitignore 2025-10-24 12:30:30 +02:00
d76ecd4660 Delete QAM/pll_constellation.gif 2025-10-24 12:30:05 +02:00
809d1a726d add .gif to gitignore 2025-10-24 12:29:51 +02:00
880d6cdfac Muller & Muller (RRC needed to be implemented...) 2025-10-24 12:28:31 +02:00
43238e304e Update README.md 2025-10-24 01:25:00 +02:00
0118505523 Delete QAM/todo.md 2025-10-23 22:40:20 +02:00
5f353f005b Update README.md 2025-10-23 22:39:34 +02:00
ddfe1bbd90 add todo 2025-10-23 22:38:47 +02:00
98ccdf857f PLL 2025-10-23 22:36:57 +02:00
bf8421abb6 PLL 2025-10-23 18:45:13 +02:00
9b652e9338 CFO & PO 2025-10-23 17:29:25 +02:00
c46136b803 1D constellation + precalculated sqrt(M) 2025-10-23 14:28:22 +02:00
20 changed files with 2257 additions and 649 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
*.txt *.txt
*.png *.png
*.jpg *.jpg
*.gif
*.dat

68
QAM/c.py Normal file
View File

@ -0,0 +1,68 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# --- Demande à l'utilisateur ---
user_input = input("GIF ? (o/n) : ").strip().lower()
GENERATE_GIF = user_input == 'o'
# --- Fichiers ---
ref_file = "constellation_ref.dat" # constellation de référence
rx_file = "constellation.dat" # symboles corrigés par la PLL
# Charger les données
ref_constel = np.loadtxt(ref_file) # colonnes I Q
rx_data = np.loadtxt(rx_file) # colonnes I Q
# Paramètres
window_size = 50 # nombre de symboles à afficher dans la fenêtre
speed_factor = 5 # pour accélérer l'animation
N_frames = len(rx_data)
# Figure
fig, ax = plt.subplots(figsize=(6,6))
ax.set_title("Rolling Constellation avec PLL")
ax.set_xlabel("I")
ax.set_ylabel("Q")
ax.grid(True)
# Constellation de référence
ax.scatter(ref_constel[:,0], ref_constel[:,1], c='red', marker='x', label='Référence')
# Points PLL
scat = ax.scatter([], [], c='blue', s=20, label='Points récents')
last_point = ax.scatter([], [], c='green', s=50, label='Dernier symbole')
ax.set_xlim(np.min(ref_constel[:,0])-0.5, np.max(ref_constel[:,0])+0.5)
ax.set_ylim(np.min(ref_constel[:,1])-0.5, np.max(ref_constel[:,1])+0.5)
ax.legend()
# Initialisation
def init():
scat.set_offsets(np.empty((0,2)))
last_point.set_offsets(np.empty((0,2)))
return scat, last_point
# Mise à jour
def update(frame):
idx = min(frame*speed_factor, N_frames)
start_idx = max(0, idx - window_size)
data_window = rx_data[start_idx:idx]
scat.set_offsets(data_window)
if len(data_window) > 0:
last_point.set_offsets(data_window[-1:])
return scat, last_point
# Création de l'animation
ani = FuncAnimation(fig, update, frames=int(N_frames/speed_factor)+1,
init_func=init, blit=True, interval=20)
# --- Sauvegarde GIF conditionnelle ---
if GENERATE_GIF:
ani.save("pll_constellation.gif", writer='pillow', fps=30)
print("GIF généré : pll_constellation.gif")
# Affichage à l'écran
plt.show()

View File

@ -1,416 +0,0 @@
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 1.34164079
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 -1.34164079
-0.44721360 0.44721360
-1.34164079 1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
0.44721360 -0.44721360
-0.44721360 0.44721360
-1.34164079 -1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-0.44721360 1.34164079
0.44721360 -1.34164079
-0.44721360 1.34164079
0.44721360 0.44721360
-0.44721360 1.34164079
0.44721360 1.34164079
-0.44721360 1.34164079
-0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 0.44721360
-0.44721360 1.34164079
1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 0.44721360
-0.44721360 1.34164079
1.34164079 0.44721360
-0.44721360 0.44721360
1.34164079 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 -1.34164079
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 1.34164079
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 -1.34164079
-0.44721360 0.44721360
-1.34164079 1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
0.44721360 -0.44721360
-0.44721360 0.44721360
-1.34164079 -1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-0.44721360 1.34164079
0.44721360 -1.34164079
-0.44721360 1.34164079
0.44721360 0.44721360
-0.44721360 1.34164079
0.44721360 1.34164079
-0.44721360 1.34164079
-0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 0.44721360
-0.44721360 1.34164079
1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 0.44721360
-0.44721360 1.34164079
1.34164079 0.44721360
-0.44721360 0.44721360
1.34164079 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 -1.34164079
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 1.34164079
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 -1.34164079
-0.44721360 0.44721360
-1.34164079 1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
0.44721360 -0.44721360
-0.44721360 0.44721360
-1.34164079 -1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-0.44721360 1.34164079
0.44721360 -1.34164079
-0.44721360 1.34164079
0.44721360 0.44721360
-0.44721360 1.34164079
0.44721360 1.34164079
-0.44721360 1.34164079
-0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 0.44721360
-0.44721360 1.34164079
1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 0.44721360
-0.44721360 1.34164079
1.34164079 0.44721360
-0.44721360 0.44721360
1.34164079 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 -1.34164079
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 1.34164079
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 -1.34164079
-0.44721360 0.44721360
-1.34164079 1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
0.44721360 -0.44721360
-0.44721360 0.44721360
-1.34164079 -1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-0.44721360 1.34164079
0.44721360 -1.34164079
-0.44721360 1.34164079
0.44721360 0.44721360
-0.44721360 1.34164079
0.44721360 1.34164079
-0.44721360 1.34164079
-0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 0.44721360
-0.44721360 1.34164079
1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 0.44721360
-0.44721360 1.34164079
1.34164079 0.44721360
-0.44721360 0.44721360
1.34164079 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 -1.34164079
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 1.34164079
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 -1.34164079
-0.44721360 0.44721360
-1.34164079 1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 1.34164079
0.44721360 -0.44721360
-0.44721360 0.44721360
-1.34164079 -1.34164079
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 1.34164079
-0.44721360 1.34164079
0.44721360 -1.34164079
-0.44721360 1.34164079
0.44721360 0.44721360
-0.44721360 1.34164079
0.44721360 1.34164079
-0.44721360 1.34164079
-0.44721360 -1.34164079
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 0.44721360
-0.44721360 0.44721360
-0.44721360 1.34164079
1.34164079 -1.34164079
-0.44721360 1.34164079
1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 0.44721360
-0.44721360 1.34164079
1.34164079 0.44721360
-0.44721360 0.44721360
1.34164079 -0.44721360
-1.34164079 1.34164079
-1.34164079 -1.34164079
-0.44721360 1.34164079
-1.34164079 -0.44721360
-0.44721360 0.44721360
-1.34164079 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
-0.44721360 -0.44721360
-0.44721360 0.44721360
1.34164079 -1.34164079

View File

@ -1,16 +0,0 @@
-1.34164079 -1.34164079
-1.34164079 -0.44721360
-1.34164079 0.44721360
-1.34164079 1.34164079
-0.44721360 -1.34164079
-0.44721360 -0.44721360
-0.44721360 0.44721360
-0.44721360 1.34164079
0.44721360 -1.34164079
0.44721360 -0.44721360
0.44721360 0.44721360
0.44721360 1.34164079
1.34164079 -1.34164079
1.34164079 -0.44721360
1.34164079 0.44721360
1.34164079 1.34164079

View File

@ -7,50 +7,49 @@ import sys, os
# -------------------- Fichiers de données -------------------- # -------------------- Fichiers de données --------------------
REF_FILE = "constellation_ref.dat" REF_FILE = "constellation_ref.dat"
RX_FILE = "constellation.dat" RX_FILE = "constellation.dat"
ERROR_FILE = "pll_error.dat" PLL_ERROR_FILE = "pll_error.dat"
MM_ERROR_FILE = "mm_timing_error.dat" # Nouveau fichier pour l'erreur M&M
# -------------------- Fonctions de chargement -------------------- # -------------------- Fonctions de chargement --------------------
def load_points(filename): def load_points(filename):
if os.path.exists(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: else:
return np.zeros((0,2)) return np.zeros((0,2))
def load_error(filename): def load_error(filename):
if os.path.exists(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: else:
return np.zeros((0,2)) return np.zeros((0,2))
# -------------------- Application -------------------- # -------------------- Application --------------------
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
win = pg.GraphicsLayoutWidget(show=True, title="Constellation") win = pg.GraphicsLayoutWidget(show=True, title="Synchronisation QAM (M&M)")
win.resize(900, 900) win.resize(1200, 800)
win.setBackground('#0a0a0a') # fond noir profond win.setBackground('#0a0a0a') # fond noir profond
plot = win.addPlot() # -------------------- Plot 1: Constellation --------------------
plot.setAspectLocked(True) # axes égaux # 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 -------------------- grid_constel = pg.GridItem()
plot.getAxis('left').setPen(pg.mkPen('#AAA', width=2)) grid_constel.setPen(pg.mkPen('#444', width=1, style=QtCore.Qt.DotLine))
plot.getAxis('bottom').setPen(pg.mkPen('#AAA', width=2)) plot_constel.addItem(grid_constel)
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)
# -------------------- Chargement initial -------------------- # -------------------- Chargement initial --------------------
ref_data = load_points(REF_FILE) ref_data = load_points(REF_FILE)
rx_data = load_points(RX_FILE) rx_data = load_points(RX_FILE)
error_data = load_error(ERROR_FILE)
# Points référence # Points référence
ref_plot = plot.plot(ref_data[:,0], ref_data[:,1], ref_plot = plot_constel.plot(ref_data[:,0], ref_data[:,1],
pen=None, pen=None,
symbol='o', symbol='o',
symbolSize=10, symbolSize=10,
@ -59,7 +58,7 @@ ref_plot = plot.plot(ref_data[:,0], ref_data[:,1],
name='Référence') name='Référence')
# Points reçus # Points reçus
rx_plot = plot.plot(rx_data[:,0], rx_data[:,1], rx_plot = plot_constel.plot(rx_data[:,0], rx_data[:,1],
pen=None, pen=None,
symbol='x', symbol='x',
symbolSize=6, symbolSize=6,
@ -67,29 +66,76 @@ rx_plot = plot.plot(rx_data[:,0], rx_data[:,1],
symbolPen=None, symbolPen=None,
name='Reçu') 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')
# -------------------- Légende stylée -------------------- # -------------------- Légende stylée --------------------
legend = plot.addLegend() legend_constel = plot_constel.addLegend()
for item in legend.items: for item in legend_constel.items:
item[1].setPen(pg.mkPen('#FFF', width=2)) 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 -------------------- # -------------------- Timer pour mise à jour dynamique --------------------
def update(): def update():
global N_val
ref_data = load_points(REF_FILE) ref_data = load_points(REF_FILE)
rx_data = load_points(RX_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: if ref_data.size > 0:
ref_plot.setData(ref_data[:,0], ref_data[:,1]) ref_plot.setData(ref_data[:,0], ref_data[:,1])
if rx_data.size > 0: if rx_data.size > 0:
rx_plot.setData(rx_data[:,0], rx_data[:,1]) 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 = QtCore.QTimer()
timer.timeout.connect(update) timer.timeout.connect(update)

BIN
QAM/out

Binary file not shown.

356
QAM/qam.c
View File

@ -208,6 +208,299 @@ void free_constellation(qam_system* qam) {
free(qam->constellation); free(qam->constellation);
} }
// Préambule QAM
void generate_preamble(qam_system* qam, double complex* preamble, int L) {
// L 1er symboles de la constellation
for (int i = 0; i < L; i++) {
preamble[i] = qam->constellation[i % qam->M];
}
}
// Concatène le préambule avec le signal modulé
double complex* concat_preamble_signal(double complex* preamble_mod, int preamble_len, double complex* s_mod, int nb_symbols, int N) {
int total_samples = (preamble_len + nb_symbols) * N;
double complex* s_concat = malloc(sizeof(double complex) * total_samples);
// Copier le préambule modulé
for (int i = 0; i < preamble_len * N; i++) {
s_concat[i] = preamble_mod[i];
}
// Copier le signal modulé après le préambule
for (int i = 0; i < nb_symbols * N; i++) {
s_concat[preamble_len * N + i] = s_mod[i];
}
return s_concat;
}
// Coarse Frequency Offset avec préambule avec N = 1 (lag 1)
void cfo(double complex* s_with_preamble, int N, int L, double Fs, int total_samples, double Fc) {
double complex sum = 0;
int lag = 5;
for (int n = lag; n < L * N; n++) {
sum += s_with_preamble[n] * conj(s_with_preamble[n - lag]);
}
double phi = carg(sum);
double f_est = (phi / (2.0 * M_PI * lag)) * Fs;
double f_offset = f_est - Fc;
printf("CFO estimé : %f Hz \n", f_offset);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= cexp(-I * 2.0 * M_PI * f_offset * n / Fs);
}
}
// Fine Frequency Offset (FFO) Correction
void ffo(qam_system* qam, double complex* s_with_preamble, double complex* preamble_ref, int L, int total_samples) {
double complex r_preamble[L];
for (int k = 0; k < L; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
r += s_with_preamble[idx] * cexp(-2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs)) / A;
}
r /= qam->N;
r_preamble[k] = r;
}
double complex diff_phase_sum = 0;
double Ts_symbole = qam->N / qam->Fs;
for (int k = 1; k < L; k++) {
double complex error_k = r_preamble[k] * conj(preamble_ref[k]);
double complex error_k_minus_1 = r_preamble[k - 1] * conj(preamble_ref[k - 1]);
diff_phase_sum += error_k * conj(error_k_minus_1);
}
double phi_sym = carg(diff_phase_sum);
double delta_f_fine_est = phi_sym / (2.0 * M_PI * Ts_symbole);
printf("FFO estimé (Delta f fine) : %f Hz\n", delta_f_fine_est);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= cexp(-I * 2.0 * M_PI * delta_f_fine_est * n / qam->Fs);
}
}
// Phase Offset (PO) Correction
void po(qam_system* qam, double complex* s_with_preamble, double complex* preamble_ref, int L, int total_samples) {
double complex r_preamble[L];
for (int k = 0; k < L; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
r += s_with_preamble[idx] * cexp(-2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs)) / A;
}
r /= qam->N;
r_preamble[k] = r;
}
double complex phase_error_sum = 0;
for (int k = 0; k < L; k++) {
phase_error_sum += r_preamble[k] * conj(preamble_ref[k]);
}
double phi_est = carg(phase_error_sum);
printf("Phase Offset (PO) estimée : %f radians (soit %f degrés)\n", phi_est, phi_est * 180.0 / M_PI);
double complex phase_corr = cexp(-I * phi_est);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= phase_corr;
}
}
// Costas loop
void pll(qam_system* qam, double complex* s_with_preamble, double complex* r_corr, int L, int total_samples, int nb_symbols, double Kp, double Ki, double alpha, FILE* fp_error) {
double phase_est = 0.0;
double integrator = 0.0;
double filtered_error = 0.0;
int start_data_idx = L * qam->N;
int N = qam->N;
for (int k = 0; k < nb_symbols; k++) {
double complex r_k = 0;
for (int n = 0; n < N; n++) {
int idx = start_data_idx + k * N + n;
double t = (double)idx / qam->Fs;
r_k += s_with_preamble[idx] * cexp(-2.0 * I * M_PI * qam->Fc * t) / A;
}
double complex r_symbol = r_k / N;
r_symbol *= cexp(-I * phase_est);
double min_dist = INFINITY;
double complex closest = 0;
for (int idx = 0; idx < qam->M; idx++) {
double dist = cabs(r_symbol - qam->constellation[idx]);
if (dist < min_dist) {
min_dist = dist;
closest = qam->constellation[idx];
}
}
double error = carg(r_symbol * conj(closest));
filtered_error = (1.0 - alpha) * filtered_error + alpha * error;
integrator += Ki * filtered_error;
phase_est += Kp * filtered_error + integrator;
if (fp_error) {
fprintf(fp_error, "%d % .8f\n", k, 5 * filtered_error);
fflush(fp_error);
}
for (int n = 0; n < N; n++) {
int idx = start_data_idx + k * N + n;
r_corr[idx] = s_with_preamble[idx] * cexp(-I * phase_est);
}
}
}
// 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, FILE* fp_error) {
double integrator = 0.0;
double timing_offset = 0.0;
double current_idx_double = 0.0;
double complex d_k_minus_1 = 0;
for (int k = 0; k < nb_symbols; k++) {
int opt_idx = (int)round(current_idx_double);
int tilde_idx = (int)round(current_idx_double + qam->N / 2.0);
if (opt_idx >= nb_symbols * qam->N || tilde_idx >= nb_symbols * qam->N || tilde_idx < 0)
break;
double complex r_k = s_data[opt_idx];
double complex r_tilde_k = s_data[tilde_idx];
double min_d = INFINITY;
double complex d_k = 0;
int decision_idx = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r_k - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
d_k = qam->constellation[idx];
decision_idx = idx;
}
}
r_mm_out[k] = r_k;
// Erreur M&M
double error_k = 0.0;
if (k > 0) {
// Formule M&M: e_k = Re{ r_tilde_k * (d_{k-1}^* - d_k^*) }
double complex error_term = r_tilde_k * conj(d_k_minus_1 - d_k);
error_k = creal(error_term);
}
// Costas Loop PI
integrator += Ki_mm * error_k;
timing_offset = Kp_mm * error_k + integrator;
current_idx_double += qam->N - timing_offset;
d_k_minus_1 = d_k;
if (fp_error) {
fprintf(fp_error, "%d % .8f\n", k, 3 * timing_offset);
fflush(fp_error);
}
}
}
// Demodulation M&M
void demodulate_sync_adapted(qam_system* qam, double complex* r_mm_out, int nb_symbols, uint8_t* bits_hat, FILE *fp_constel) {
for (int k = 0; k < nb_symbols; k++) {
double complex r = r_mm_out[k];
if (fp_constel) {
fprintf(fp_constel, "% .8f % .8f\n", creal(r), cimag(r));
fflush(fp_constel);
}
// Distance euclidien (quantification/décision)
double min_d = INFINITY;
int i_cl = 0;
int j_cl = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
i_cl = idx / qam->sm;
j_cl = idx % qam->sm;
}
}
// Gray mapping et extraction des bits
if (qam->k % 2 != 0) {
printf("demodulate : k doit être pair (k = %d)\n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
int i_bin = gray_to_bin(i_cl);
int j_bin = gray_to_bin(j_cl);
for (int b = 0; b < bits_per_axis; b++) {
bits_hat[k * qam->k + b] = (i_bin >> (bits_per_axis - 1 - b)) & 1;
bits_hat[k * qam->k + bits_per_axis + b] = (j_bin >> (bits_per_axis - 1 - b)) & 1;
}
}
}
void demodulate_carrier(qam_system* qam, double complex* s_input, double complex* s_bandebase, int total_samples) {
for (int n = 0; n < total_samples; n++) {
double t = (double)n / qam->Fs;
s_bandebase[n] = s_input[n] * cexp(-I * 2 * M_PI * qam->Fc * t) / A;
}
}
// Réponse impulsionnelle du filtre RRC
void rrc_impulse_response(qam_system* qam, int N_taps, double alpha, double* rrc_taps) {
double Ts_symbol = qam->N / qam->Fs;
double t;
int k;
for (int i = 0; i < N_taps; i++) {
t = ((double)i - (N_taps - 1.0) / 2.0) / qam->Fs;
k = i;
double t_norm = t / Ts_symbol;
if (fabs(t) < 1e-9) { // t = 0
rrc_taps[k] = (1.0 / Ts_symbol) * (1.0 + alpha * (4.0 / M_PI) * (1.0 / 4.0));
} else if (fabs(fabs(t_norm) - 1.0 / (4.0 * alpha)) < 1e-9) {
rrc_taps[k] = (1.0 / Ts_symbol) * (alpha / M_PI) * ( (M_PI / 4.0) * (1.0 / alpha) + 1.0) * sin(M_PI / (4.0 * alpha)) + (1.0 / Ts_symbol) * (alpha / M_PI) * ( (M_PI / 4.0) * (1.0 / alpha) - 1.0) * cos(M_PI / (4.0 * alpha));
} else {
double num = sin(M_PI * t_norm * (1.0 - alpha)) + (4.0 * alpha * t_norm) * cos(M_PI * t_norm * (1.0 + alpha));
double den = (M_PI * t_norm * (1.0 - (4.0 * alpha * t_norm) * (4.0 * alpha * t_norm)));
rrc_taps[k] = (1.0 / Ts_symbol) * num / den;
}
}
}
// Séquence d'impulsions de Dirac en base band
void generate_baseband_pulses(qam_system* qam, double complex* symbols, int L_symbols, double complex* s_pulses) {
int total_samples = L_symbols * qam->N;
memset(s_pulses, 0, total_samples * sizeof(double complex));
for (int k = 0; k < L_symbols; k++) {
int idx = k * qam->N;
s_pulses[idx] = symbols[k];
}
}
int main () { int main () {
// Initialisation du system qam // Initialisation du system qam
qam_system qam; qam_system qam;
@ -221,7 +514,7 @@ int main () {
init_constellation(&qam); init_constellation(&qam);
// Conversion du texte en bits // 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_chars = strlen(texte);
int nb_bits = nb_chars * 8; int nb_bits = nb_chars * 8;
int nb_symbols = (nb_bits + qam.k - 1) / qam.k; int nb_symbols = (nb_bits + qam.k - 1) / qam.k;
@ -232,47 +525,86 @@ int main () {
double complex* symbols = malloc(sizeof(double complex) * nb_symbols); double complex* symbols = malloc(sizeof(double complex) * nb_symbols);
bits_to_symbols(&qam, input_bits, nb_bits, symbols); bits_to_symbols(&qam, input_bits, nb_bits, symbols);
// Modulation // Initialisation du préambule
int total_samples = qam.N * nb_symbols; int L = 15;
int total_samples = qam.N * (nb_symbols + L);
double complex* preamble = malloc(sizeof(double complex) * L);
generate_preamble(&qam, preamble, L);
double complex* preamble_mod = malloc(sizeof(double complex) * L * qam.N);
modulate(&qam, preamble, L, preamble_mod);
double complex* s_mod = malloc(sizeof(double complex) * nb_symbols * qam.N);
modulate(&qam, symbols, nb_symbols, s_mod);
double complex* s = malloc(sizeof(double complex) * total_samples); double complex* s = malloc(sizeof(double complex) * total_samples);
modulate(&qam, symbols, nb_symbols, s); double complex* s_with_preamble = concat_preamble_signal(preamble_mod, L, s_mod, nb_symbols, qam.N);
// Ajout du bruit // Ajout du bruit
add_noise(s, total_samples, 0); add_noise(s_with_preamble, total_samples, 2);
FILE *fp_ref = fopen("constellation_ref.dat", "w"); FILE *fp_ref = fopen("constellation_ref.dat", "w");
fill_constellation_data(&qam, fp_ref); fill_constellation_data(&qam, fp_ref);
fclose(fp_ref); fclose(fp_ref);
// Ajout de dephasage // Ajout de dephasage
//add_dephasage(s, M_PI / 6.0, total_samples); add_dephasage(s_with_preamble, M_PI / 2.0 , total_samples);
// AJout de decalage de fréquence // AJout de decalage de fréquence
//add_freq(&qam, s, 1, total_samples); add_freq(&qam, s_with_preamble, 100, total_samples);
// Estimation / correction du CFO
cfo(s_with_preamble, qam.N, L, qam.Fs, total_samples, qam.Fc);
ffo(&qam, s_with_preamble, preamble, L, total_samples);
// Correction phase
po(&qam, s_with_preamble, preamble, L, total_samples);
// PLL
double complex* s_corrected = malloc(sizeof(double complex) * total_samples);
double Kp = 0.03;
double Ki = 0.002;
double alpha = 0.1;
FILE *fp_pll_error = fopen("pll_error.dat", "w");
pll(&qam, s_with_preamble, s_corrected, L, total_samples, nb_symbols, Kp, Ki, alpha, fp_pll_error);
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, nb_symbols * qam.N);
double complex* r_mm_out = malloc(sizeof(double complex) * nb_symbols);
double Kp_mm = 0.6;
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 // Démodulation
FILE *fp_constel = fopen("constellation.dat", "w"); FILE *fp_constel = fopen("constellation.dat", "w");
uint8_t* output_bits = (uint8_t*)malloc(nb_bits * sizeof(uint8_t)); uint8_t* output_bits = (uint8_t*)malloc(nb_bits * sizeof(uint8_t));
demodulate(&qam, s, nb_symbols, output_bits, fp_constel); demodulate_sync_adapted(&qam, r_mm_out, nb_symbols, output_bits, fp_constel);
//demodulate(&qam, s_corrected + L * qam.N, nb_symbols, output_bits, fp_constel);
fclose(fp_constel); fclose(fp_constel);
// Reconstruction du texte // Reconstruction du texte
char* texte_recup = malloc(nb_chars + 1); char* texte_recup = malloc(nb_chars + 1);
reconstruction_text(nb_chars, output_bits, texte_recup); reconstruction_text(nb_chars, output_bits, texte_recup);
printf("Texte original : %s\n\n", texte); //printf("Texte original : %s\n\n", texte);
printf("Texte demodulé : %s\n", texte_recup); printf("Texte demodulé : %s\n", texte_recup);
// Calcul du BER // Calcul du BER
double ber = compare_bits(input_bits, output_bits, nb_bits); double ber = compare_bits(input_bits, output_bits, nb_bits);
printf("Taux d'erreur blind QAM: %.4f\n", ber * 100); printf("Taux d'erreur QAM: %.4f\n", ber * 100);
// Libération mémoire // Libération mémoire
free(input_bits); free(input_bits);
free(output_bits); free(output_bits);
free(symbols); free(symbols);
free(s); free(preamble);
free(preamble_mod);
free(s_with_preamble);
free(texte_recup);
free_constellation(&qam); free_constellation(&qam);
return 0; return 0;
} }

View File

@ -1,157 +0,0 @@
# 🧭 Ordre dimplémentation dune chaîne de réception QAM (réelle)
## **Étape 0 — Environnement et base de test**
🎯 Objectif : avoir une base stable de simulation avant toute boucle adaptative.
**À faire :**
* Lire un **flux IQ** (fichier ou SDR)
* Implémenter un **filtre RRC de réception**
* Visualiser constellations, spectre, symboles
🧠 But : voir des symboles flous mais reconnaissables — aucun algorithme adaptatif encore.
---
## **Étape 1 — Correction de fréquence (CFO / Carrier Recovery)**
🎯 Objectif : supprimer le décalage de fréquence de la porteuse avant le timing recovery.
**Pourquoi en premier ?**
* Si ton signal tourne dans le plan complexe, **Mueller & Müller échouera** complètement.
* Il faut un signal “quasistationnaire” avant de chercher le bon instant déchantillonnage.
**Méthodes à implémenter :**
* Estimation grossière de CFO (par corrélation / FFT)
* Boucle de Costas ou PLL de phase
📘 Résultat attendu : constellation QAM fixe mais “floue” (problème de timing restant).
---
## **Étape 2 — Synchronisation temporelle (Timing Recovery : M&M)**
🎯 Objectif : trouver linstant exact déchantillonnage par symbole.
**Tu connais déjà :**
* Interpolateur fractionnaire
* Détecteur derreur M&M
* Boucle PI
💡 Astuce : commence avec un signal *parfaitement corrigé en fréquence* avant dactiver la boucle timing.
📘 Résultat attendu : points de constellation bien centrés, toujours un peu brouillés (canal non corrigé).
---
## **Étape 3 — Égalisation adaptative (FFE / CMA / DD-LMS)**
🎯 Objectif : supprimer lISI et compenser la distorsion de canal.
**Méthodes typiques :**
* **CMA** (Constant Modulus Algorithm) pour pré-verrouillage (aveugle)
* Puis **DD-LMS** (Decision Directed) une fois la décision fiable
💡 Légaliseur doit venir **après** le timing (sinon le signal est mal échantillonné).
📘 Résultat attendu : constellation nette, points regroupés correctement autour des symboles 16-QAM.
---
## **Étape 4 — Décision + Mapping**
🎯 Objectif : convertir les symboles QAM en bits.
**À faire :**
* Implémenter la décision dure (±1, ±3)
* Mapping / demapping Gray
* Vérifier BER par rapport à trame connue
💡 Teste dabord sans bruit pour valider le mapping bit ↔ symbole.
---
## **Étape 5 — Boucles de phase fines (Costas loop fine)**
🎯 Objectif : corriger la phase résiduelle après timing et égalisation.
* Souvent intégrée dans la boucle M&M ou séparée (PLL de phase fine)
* Sert à verrouiller la dernière rotation du plan IQ
---
## **Étape 6 — Correction derreurs / décodage (FEC)**
🎯 Objectif : terminer la chaîne par le décodage des bits.
* LDPC, Viterbi, Turbo selon ton système
* Cest la couche “bitstream”, plus logique que DSP
---
## **Étape 7 — Optimisation et intégration**
🎯 Objectif : passer du prototype à la version embarquée.
* Conversion float → fixe (Q-format)
* Pipeline temps réel (DMA, buffers circulaires)
* Profiling CPU / mémoire
* Test sur matériel SDR, puis en RF réelle
---
# ⚙️ En résumé — Ordre dimplémentation
| Étape | Bloc | Type | Pourquoi cet ordre |
| ----- | ------------------------------------ | --------------- | --------------------------- |
| 0 | RRC + acquisition IQ | statique | Base stable et visualisable |
| 1 | **Correction de fréquence (CFO)** | boucle 1 | sinon M&M échoue |
| 2 | **Synchronisation temporelle (M&M)** | boucle 2 | aligner les symboles |
| 3 | **Égalisation adaptative** | boucle 3 | corriger canal |
| 4 | **Décision + mapping bits** | logique | extraire données |
| 5 | **Boucle de phase fine (Costas)** | ajustement | phase finale |
| 6 | **Décodage FEC** | post-traitement | fiabiliser le bitstream |
| 7 | **Optimisation C/FPGA** | système | rendre temps réel |
---
# 🧠 Ordre de test conseillé
1. Simule tout en **float** dans Python/MATLAB
2. Valide chaque bloc **indépendamment**
3. Assemble et teste avec bruit / décalage
4. **Ensuite seulement**, porte en C (ou sur DSP/FPGA)
---
---
| # | Bloc / Étape | Objectif principal | Priorité | Ce quil faut coder / comprendre | Test / Validation |
| - | --------------------------------------- | ----------------------------------------------- | ---------- | ----------------------------------------------- | ------------------------------------------------ |
| 0 | **Acquisition & Filtrage RRC** | Lire le signal IQ et filtrer pour limiter lISI | Très haute | FIR RRC, buffer IQ | Visualiser constellation, spectre |
| 1 | **Correction de fréquence (CFO)** | Supprimer offset de fréquence de la porteuse | Très haute | PLL / Costas loop, corrélation, FFT | Constellation immobile, pas de rotation |
| 2 | **Synchronisation temporelle (M&M)** | Aligner échantillons sur symboles | Très haute | Interpolateur fractionnaire, TED M&M, boucle PI | Points centrés, vérification de tau_hat |
| 3 | **Égalisation adaptative** | Supprimer ISI et compenser canal | Haute | FFE ou DFE, algorithme CMA / DD-LMS | Constellation nette, erreur moyenne faible |
| 4 | **Décision symbolique et mapping** | Convertir symbole → bits | Haute | Hard decision QAM, Gray mapping | Vérifier BER avec trame connue |
| 5 | **Boucle de phase fine (Costas / PLL)** | Corriger la phase résiduelle | Moyenne | PLL numérique, phase fine | Points de constellation fixes, phase verrouillée |
| 6 | **Décodage FEC** | Extraire flux de bits fiable | Moyenne | LDPC / Viterbi / Turbo | Comparer bits reçus / transmis, BER |
| 7 | **Optimisation & passage temps réel** | Adapter pour C / DSP / FPGA | Moyenne | Point fixe, buffers circulaires, pipeline | Profil CPU / mémoire, latence, test en SDR réel |
---
### 💡 Notes pratiques :
* **Test bloc par bloc** avant dintégrer la chaîne complète
* Toujours **simuler en float** avant passage en C ou point fixe
* Chaque boucle (CFO, Timing, Phase) doit être **réglée indépendamment** pour éviter linstabilité
* Commencer avec **trames simples** avant bruit réel, puis ajouter AWGN / jitter / offsets
---

View File

@ -1 +1,7 @@
DSP related implementations. DSP related implementations.
TODO :
- RRC
- Auto gain control (M-QAM)
- ~Muller & Muller~
- Eye diagram

104
saveQAM/3/debug.py Normal file
View File

@ -0,0 +1,104 @@
#!/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"
ERROR_FILE = "pll_error.dat"
# -------------------- Fonctions de chargement --------------------
def load_points(filename):
if os.path.exists(filename):
return np.loadtxt(filename)
else:
return np.zeros((0,2))
def load_error(filename):
if os.path.exists(filename):
return np.loadtxt(filename)
else:
return np.zeros((0,2))
# -------------------- Application --------------------
app = QtWidgets.QApplication(sys.argv)
win = pg.GraphicsLayoutWidget(show=True, title="Constellation")
win.resize(900, 900)
win.setBackground('#0a0a0a') # fond noir profond
plot = win.addPlot()
plot.setAspectLocked(True) # axes égaux
# -------------------- 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)
# -------------------- 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')
# 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')
# -------------------- Légende stylée --------------------
legend = plot.addLegend()
for item in legend.items:
item[1].setPen(pg.mkPen('#FFF', width=2))
# -------------------- Timer pour mise à jour dynamique --------------------
def update():
ref_data = load_points(REF_FILE)
rx_data = load_points(RX_FILE)
error_data = load_error(ERROR_FILE)
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])
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_())

278
saveQAM/3/qam.c Normal file
View File

@ -0,0 +1,278 @@
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
#include <string.h>
#define A 10
struct qam_system_s {
int M; // Nombre de symboles M-QAM
int k; // Nombre de bits/symboles
int sm; // sqrt M
double Fs; // Fréquence d'échantillionage
double Ts; // Temps d'échantillionage
int N; // Nombre d'échantillions
double Fc; // Fréquence de la porteuse
double complex* constellation; // Tableau de symboles I + j Q 1D
};
typedef struct qam_system_s qam_system;
// Initialisation de la constellation (double tableau de taille sqrt(M)),
void init_constellation (qam_system* qam) {
qam->constellation = (double complex*)malloc(sizeof(double complex) * qam->M);
double norm_factor = sqrt((double)(qam->M - 1) / 3.0); // Pour puissance unitaire
for (int i = 0; i < qam->sm; i++) {
double ip = -(qam->sm - 1) + 2 * i;
for (int j = 0; j < qam->sm; j++) {
double qp = -(qam->sm - 1) + 2 * j;
int idx = i * qam->sm + j;
qam->constellation[idx] = (ip + I * qp) / norm_factor;
}
}
}
int bin_to_gray (int x) {
return x ^ (x >> 1);
}
// Iteration de XOR
int gray_to_bin(int g) {
int b = g;
while (g >>= 1)
b ^= g;
return b;
}
// Modulation QAM
void modulate (qam_system* qam, double complex* symbols, int nb_symbols, double complex* s) {
for (int k = 0; k < nb_symbols; k++) {
double complex iq = symbols[k];
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
s[idx] = A * iq * cexp(2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs));
}
}
}
// Demodulation QAM
void demodulate(qam_system* qam, double complex* s, int nb_symbols, uint8_t* bits_hat, FILE *fp_constel) {
for (int k = 0; k < nb_symbols; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
r += s[k * qam->N + n] * cexp(-2 * I * M_PI * qam->Fc * ((double)(k * qam->N + n) / qam->Fs)) / A;
}
r /= qam->N;
if (fp_constel) {
fprintf(fp_constel, "% .8f % .8f\n", creal(r), cimag(r));
fflush(fp_constel);
}
// Distance euclidien de Ir et Qr pour avoir le point le plus proche de la constellation (lent)
double min_d = INFINITY;
int i_cl = 0;
int j_cl = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
i_cl = idx / qam->sm;
j_cl = idx % qam->sm;
}
}
// Gray mapping
if (qam->k % 2 != 0) {
printf("demodulate : k pair (k = %d)\n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
int i_bin = gray_to_bin(i_cl);
int j_bin = gray_to_bin(j_cl);
for (int b = 0; b < bits_per_axis; b++) {
bits_hat[k * qam->k + b] = (i_bin >> (bits_per_axis - 1 - b)) & 1;
bits_hat[k * qam->k + bits_per_axis + b] = (j_bin >> (bits_per_axis - 1 - b)) & 1;
}
}
}
// Calcul du bruit gaussien pour un sigma donné
// Formule de Box-Muller
double gaussian_noise (double sigma) {
double u1 = (rand() + 1) / ((double)RAND_MAX + 2);
double u2 = (rand() + 1) / ((double)RAND_MAX + 2);
return sigma * sqrt(-2 * log(u1)) * cos(2 * M_PI * u2);
}
// Ajout du bruit
void add_noise (double complex* s, int len, double sigma) {
//double signal_power = (2.0/3.0)*(qam.M-1);
//double snr_dB = 5; // SNR en dB
//double snr_lin = pow(10.0, snr_dB / 10.0);
//double sigma = sqrt(signal_power / snr_lin);
for (int i = 0; i < len; i++) {
double nr = gaussian_noise(sigma);
double ni = gaussian_noise(sigma);
s[i] += nr + I * ni;
}
}
// Changer le tableau de bits en boolen ou alors la represenation binaire et shifter pour extraire les bits (pas bien si M plus grand)
void bits_to_symbols (qam_system* qam, uint8_t* bits, int nb_bits, double complex* symbols) {
int nb_symbols = nb_bits / qam->k;
// k pair
if (qam->k % 2 != 0) {
printf("bits_to_symbols : k doit être pair (k = %d) \n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
for (int sym = 0; sym < nb_symbols; sym++) {
// Construire les indices binaires i_bin (MSB...) et j_bin (LSB...)
int i_bin = 0;
int j_bin = 0;
for (int b = 0; b < bits_per_axis; b++) {
i_bin = (i_bin << 1) | bits[sym * qam->k + b];
j_bin = (j_bin << 1) | bits[sym * qam->k + bits_per_axis + b];
}
int i_gray = bin_to_gray(i_bin);
int j_gray = bin_to_gray(j_bin);
if (i_gray < 0 || i_gray >= qam->sm || j_gray < 0 || j_gray >= qam->sm) {
printf("bits_to_symbols : IOOR i_gray = %d j_gray = %d sm = %d \n", i_gray, j_gray, qam->sm);
exit(1);
}
symbols[sym] = qam->constellation[i_gray * qam->sm + j_gray];
}
}
double compare_bits(uint8_t* bits1, uint8_t* bits2, int nb_bits) {
int errors = 0;
for (int i = 0; i < nb_bits; i++) {
if (bits1[i] != bits2[i]) errors++;
}
return (double)errors / nb_bits;
}
void add_dephasage(double complex* s, double phi_offset, int total_samples) {
for (int i = 0; i < total_samples; i++) {
s[i] *= cexp(I * phi_offset);
}
}
void add_freq(qam_system* qam, double complex* s, double freq_offset, int total_samples) {
for (int i = 0; i < total_samples; i++) {
double t = (double)i / qam->Fs;
s[i] *= cexp(I * 2 * M_PI * freq_offset * t);
}
}
void fill_constellation_data(qam_system* qam, FILE *fp_ref) {
for (int i = 0; i < qam->sm; i++) {
for (int j = 0; j < qam->sm; j++) {
int idx = i * qam->sm + j;
fprintf(fp_ref, "% .8f % .8f\n", creal(qam->constellation[idx]), cimag(qam->constellation[idx]));
}
}
}
void reconstruction_text(int nb_chars, uint8_t* output_bits, char* texte_recup) {
for(int i = 0; i < nb_chars; i++){
char c = 0;
for(int b = 0; b < 8; b++){
c |= output_bits[i*8 + b] << (7-b);
}
texte_recup[i] = c;
}
texte_recup[nb_chars] = '\0';
}
void text_to_bits(int nb_chars, int nb_bits, char* texte, uint8_t* input_bits) {
for(int i = 0; i < nb_chars; i++){
for(int b = 0; b < 8; b++){
input_bits[i*8 + b] = (texte[i] >> (7-b)) & 1;
}
}
}
// Libération de la mémoire
void free_constellation(qam_system* qam) {
free(qam->constellation);
}
int main () {
// Initialisation du system qam
qam_system qam;
qam.M = 16;
qam.k = (int)log2((double)(qam.M));
qam.sm = (int)sqrt(qam.M);
qam.Fs = 44100;
qam.Ts = 0.01;
qam.N = (int)(qam.Fs * qam.Ts);
qam.Fc = 2000;
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";
int nb_chars = strlen(texte);
int nb_bits = nb_chars * 8;
int nb_symbols = (nb_bits + qam.k - 1) / qam.k;
uint8_t* input_bits = malloc(nb_bits * sizeof(uint8_t));
text_to_bits(nb_chars, nb_bits, texte, input_bits);
// Conversion en symboles
double complex* symbols = malloc(sizeof(double complex) * nb_symbols);
bits_to_symbols(&qam, input_bits, nb_bits, symbols);
// Modulation
int total_samples = qam.N * nb_symbols;
double complex* s = malloc(sizeof(double complex) * total_samples);
modulate(&qam, symbols, nb_symbols, s);
// Ajout du bruit
add_noise(s, total_samples, 0);
FILE *fp_ref = fopen("constellation_ref.dat", "w");
fill_constellation_data(&qam, fp_ref);
fclose(fp_ref);
// Ajout de dephasage
//add_dephasage(s, M_PI / 6.0, total_samples);
// AJout de decalage de fréquence
//add_freq(&qam, s, 1, total_samples);
// Démodulation
FILE *fp_constel = fopen("constellation.dat", "w");
uint8_t* output_bits = (uint8_t*)malloc(nb_bits * sizeof(uint8_t));
demodulate(&qam, s, nb_symbols, output_bits, fp_constel);
fclose(fp_constel);
// Reconstruction du texte
char* texte_recup = malloc(nb_chars + 1);
reconstruction_text(nb_chars, output_bits, texte_recup);
printf("Texte original : %s\n\n", texte);
printf("Texte demodulé : %s\n", texte_recup);
// Calcul du BER
double ber = compare_bits(input_bits, output_bits, nb_bits);
printf("Taux d'erreur blind QAM: %.4f\n", ber * 100);
// Libération mémoire
free(input_bits);
free(output_bits);
free(symbols);
free(s);
free_constellation(&qam);
return 0;
}

68
saveQAM/4/c.py Normal file
View File

@ -0,0 +1,68 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# --- Demande à l'utilisateur ---
user_input = input("GIF ? (o/n) : ").strip().lower()
GENERATE_GIF = user_input == 'o'
# --- Fichiers ---
ref_file = "constellation_ref.dat" # constellation de référence
rx_file = "constellation.dat" # symboles corrigés par la PLL
# Charger les données
ref_constel = np.loadtxt(ref_file) # colonnes I Q
rx_data = np.loadtxt(rx_file) # colonnes I Q
# Paramètres
window_size = 50 # nombre de symboles à afficher dans la fenêtre
speed_factor = 5 # pour accélérer l'animation
N_frames = len(rx_data)
# Figure
fig, ax = plt.subplots(figsize=(6,6))
ax.set_title("Rolling Constellation avec PLL")
ax.set_xlabel("I")
ax.set_ylabel("Q")
ax.grid(True)
# Constellation de référence
ax.scatter(ref_constel[:,0], ref_constel[:,1], c='red', marker='x', label='Référence')
# Points PLL
scat = ax.scatter([], [], c='blue', s=20, label='Points récents')
last_point = ax.scatter([], [], c='green', s=50, label='Dernier symbole')
ax.set_xlim(np.min(ref_constel[:,0])-0.5, np.max(ref_constel[:,0])+0.5)
ax.set_ylim(np.min(ref_constel[:,1])-0.5, np.max(ref_constel[:,1])+0.5)
ax.legend()
# Initialisation
def init():
scat.set_offsets(np.empty((0,2)))
last_point.set_offsets(np.empty((0,2)))
return scat, last_point
# Mise à jour
def update(frame):
idx = min(frame*speed_factor, N_frames)
start_idx = max(0, idx - window_size)
data_window = rx_data[start_idx:idx]
scat.set_offsets(data_window)
if len(data_window) > 0:
last_point.set_offsets(data_window[-1:])
return scat, last_point
# Création de l'animation
ani = FuncAnimation(fig, update, frames=int(N_frames/speed_factor)+1,
init_func=init, blit=True, interval=20)
# --- Sauvegarde GIF conditionnelle ---
if GENERATE_GIF:
ani.save("pll_constellation.gif", writer='pillow', fps=30)
print("GIF généré : pll_constellation.gif")
# Affichage à l'écran
plt.show()

104
saveQAM/4/debug.py Normal file
View File

@ -0,0 +1,104 @@
#!/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"
ERROR_FILE = "pll_error.dat"
# -------------------- Fonctions de chargement --------------------
def load_points(filename):
if os.path.exists(filename):
return np.loadtxt(filename)
else:
return np.zeros((0,2))
def load_error(filename):
if os.path.exists(filename):
return np.loadtxt(filename)
else:
return np.zeros((0,2))
# -------------------- Application --------------------
app = QtWidgets.QApplication(sys.argv)
win = pg.GraphicsLayoutWidget(show=True, title="Constellation")
win.resize(900, 900)
win.setBackground('#0a0a0a') # fond noir profond
plot = win.addPlot()
plot.setAspectLocked(True) # axes égaux
# -------------------- 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)
# -------------------- 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')
# 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')
# -------------------- Légende stylée --------------------
legend = plot.addLegend()
for item in legend.items:
item[1].setPen(pg.mkPen('#FFF', width=2))
# -------------------- Timer pour mise à jour dynamique --------------------
def update():
ref_data = load_points(REF_FILE)
rx_data = load_points(RX_FILE)
error_data = load_error(ERROR_FILE)
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])
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_())

463
saveQAM/4/qam.c Normal file
View File

@ -0,0 +1,463 @@
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
#include <string.h>
#define A 10
struct qam_system_s {
int M; // Nombre de symboles M-QAM
int k; // Nombre de bits/symboles
int sm; // sqrt M
double Fs; // Fréquence d'échantillionage
double Ts; // Temps d'échantillionage
int N; // Nombre d'échantillions
double Fc; // Fréquence de la porteuse
double complex* constellation; // Tableau de symboles I + j Q 1D
};
typedef struct qam_system_s qam_system;
// Initialisation de la constellation (double tableau de taille sqrt(M)),
void init_constellation (qam_system* qam) {
qam->constellation = (double complex*)malloc(sizeof(double complex) * qam->M);
double norm_factor = sqrt((double)(qam->M - 1) / 3.0); // Pour puissance unitaire
for (int i = 0; i < qam->sm; i++) {
double ip = -(qam->sm - 1) + 2 * i;
for (int j = 0; j < qam->sm; j++) {
double qp = -(qam->sm - 1) + 2 * j;
int idx = i * qam->sm + j;
qam->constellation[idx] = (ip + I * qp) / norm_factor;
}
}
}
int bin_to_gray (int x) {
return x ^ (x >> 1);
}
// Iteration de XOR
int gray_to_bin(int g) {
int b = g;
while (g >>= 1)
b ^= g;
return b;
}
// Modulation QAM
void modulate (qam_system* qam, double complex* symbols, int nb_symbols, double complex* s) {
for (int k = 0; k < nb_symbols; k++) {
double complex iq = symbols[k];
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
s[idx] = A * iq * cexp(2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs));
}
}
}
// Demodulation QAM
void demodulate(qam_system* qam, double complex* s, int nb_symbols, uint8_t* bits_hat, FILE *fp_constel) {
for (int k = 0; k < nb_symbols; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
r += s[k * qam->N + n] * cexp(-2 * I * M_PI * qam->Fc * ((double)(k * qam->N + n) / qam->Fs)) / A;
}
r /= qam->N;
if (fp_constel) {
fprintf(fp_constel, "% .8f % .8f\n", creal(r), cimag(r));
fflush(fp_constel);
}
// Distance euclidien de Ir et Qr pour avoir le point le plus proche de la constellation (lent)
double min_d = INFINITY;
int i_cl = 0;
int j_cl = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
i_cl = idx / qam->sm;
j_cl = idx % qam->sm;
}
}
// Gray mapping
if (qam->k % 2 != 0) {
printf("demodulate : k pair (k = %d)\n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
int i_bin = gray_to_bin(i_cl);
int j_bin = gray_to_bin(j_cl);
for (int b = 0; b < bits_per_axis; b++) {
bits_hat[k * qam->k + b] = (i_bin >> (bits_per_axis - 1 - b)) & 1;
bits_hat[k * qam->k + bits_per_axis + b] = (j_bin >> (bits_per_axis - 1 - b)) & 1;
}
}
}
// Calcul du bruit gaussien pour un sigma donné
// Formule de Box-Muller
double gaussian_noise (double sigma) {
double u1 = (rand() + 1) / ((double)RAND_MAX + 2);
double u2 = (rand() + 1) / ((double)RAND_MAX + 2);
return sigma * sqrt(-2 * log(u1)) * cos(2 * M_PI * u2);
}
// Ajout du bruit
void add_noise (double complex* s, int len, double sigma) {
//double signal_power = (2.0/3.0)*(qam.M-1);
//double snr_dB = 5; // SNR en dB
//double snr_lin = pow(10.0, snr_dB / 10.0);
//double sigma = sqrt(signal_power / snr_lin);
for (int i = 0; i < len; i++) {
double nr = gaussian_noise(sigma);
double ni = gaussian_noise(sigma);
s[i] += nr + I * ni;
}
}
// Changer le tableau de bits en boolen ou alors la represenation binaire et shifter pour extraire les bits (pas bien si M plus grand)
void bits_to_symbols (qam_system* qam, uint8_t* bits, int nb_bits, double complex* symbols) {
int nb_symbols = nb_bits / qam->k;
// k pair
if (qam->k % 2 != 0) {
printf("bits_to_symbols : k doit être pair (k = %d) \n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
for (int sym = 0; sym < nb_symbols; sym++) {
// Construire les indices binaires i_bin (MSB...) et j_bin (LSB...)
int i_bin = 0;
int j_bin = 0;
for (int b = 0; b < bits_per_axis; b++) {
i_bin = (i_bin << 1) | bits[sym * qam->k + b];
j_bin = (j_bin << 1) | bits[sym * qam->k + bits_per_axis + b];
}
int i_gray = bin_to_gray(i_bin);
int j_gray = bin_to_gray(j_bin);
if (i_gray < 0 || i_gray >= qam->sm || j_gray < 0 || j_gray >= qam->sm) {
printf("bits_to_symbols : IOOR i_gray = %d j_gray = %d sm = %d \n", i_gray, j_gray, qam->sm);
exit(1);
}
symbols[sym] = qam->constellation[i_gray * qam->sm + j_gray];
}
}
double compare_bits(uint8_t* bits1, uint8_t* bits2, int nb_bits) {
int errors = 0;
for (int i = 0; i < nb_bits; i++) {
if (bits1[i] != bits2[i]) errors++;
}
return (double)errors / nb_bits;
}
void add_dephasage(double complex* s, double phi_offset, int total_samples) {
for (int i = 0; i < total_samples; i++) {
s[i] *= cexp(I * phi_offset);
}
}
void add_freq(qam_system* qam, double complex* s, double freq_offset, int total_samples) {
for (int i = 0; i < total_samples; i++) {
double t = (double)i / qam->Fs;
s[i] *= cexp(I * 2 * M_PI * freq_offset * t);
}
}
void fill_constellation_data(qam_system* qam, FILE *fp_ref) {
for (int i = 0; i < qam->sm; i++) {
for (int j = 0; j < qam->sm; j++) {
int idx = i * qam->sm + j;
fprintf(fp_ref, "% .8f % .8f\n", creal(qam->constellation[idx]), cimag(qam->constellation[idx]));
}
}
}
void reconstruction_text(int nb_chars, uint8_t* output_bits, char* texte_recup) {
for(int i = 0; i < nb_chars; i++){
char c = 0;
for(int b = 0; b < 8; b++){
c |= output_bits[i*8 + b] << (7-b);
}
texte_recup[i] = c;
}
texte_recup[nb_chars] = '\0';
}
void text_to_bits(int nb_chars, int nb_bits, char* texte, uint8_t* input_bits) {
for(int i = 0; i < nb_chars; i++){
for(int b = 0; b < 8; b++){
input_bits[i*8 + b] = (texte[i] >> (7-b)) & 1;
}
}
}
// Libération de la mémoire
void free_constellation(qam_system* qam) {
free(qam->constellation);
}
// Préambule QAM
void generate_preamble(qam_system* qam, double complex* preamble, int L) {
// L 1er symboles de la constellation
for (int i = 0; i < L; i++) {
preamble[i] = qam->constellation[i % qam->M];
}
}
// Concatène le préambule avec le signal modulé
double complex* concat_preamble_signal(double complex* preamble_mod, int preamble_len, double complex* s_mod, int nb_symbols, int N) {
int total_samples = (preamble_len + nb_symbols) * N;
double complex* s_concat = malloc(sizeof(double complex) * total_samples);
// Copier le préambule modulé
for (int i = 0; i < preamble_len * N; i++) {
s_concat[i] = preamble_mod[i];
}
// Copier le signal modulé après le préambule
for (int i = 0; i < nb_symbols * N; i++) {
s_concat[preamble_len * N + i] = s_mod[i];
}
return s_concat;
}
// Coarse Frequency Offset avec préambule avec N = 1 (lag 1)
void cfo(double complex* s_with_preamble, int N, int L, double Fs, int total_samples, double Fc) {
double complex sum = 0;
int lag = 5;
for (int n = lag; n < L * N; n++) {
sum += s_with_preamble[n] * conj(s_with_preamble[n - lag]);
}
double phi = carg(sum);
double f_est = (phi / (2.0 * M_PI * lag)) * Fs;
double f_offset = f_est - Fc;
printf("CFO estimé : %f Hz \n", f_offset);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= cexp(-I * 2.0 * M_PI * f_offset * n / Fs);
}
}
// Fine Frequency Offset (FFO) Correction
void ffo(qam_system* qam, double complex* s_with_preamble, double complex* preamble_ref, int L, int total_samples) {
double complex r_preamble[L];
for (int k = 0; k < L; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
r += s_with_preamble[idx] * cexp(-2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs)) / A;
}
r /= qam->N;
r_preamble[k] = r;
}
double complex diff_phase_sum = 0;
double Ts_symbole = qam->N / qam->Fs;
for (int k = 1; k < L; k++) {
double complex error_k = r_preamble[k] * conj(preamble_ref[k]);
double complex error_k_minus_1 = r_preamble[k - 1] * conj(preamble_ref[k - 1]);
diff_phase_sum += error_k * conj(error_k_minus_1);
}
double phi_sym = carg(diff_phase_sum);
double delta_f_fine_est = phi_sym / (2.0 * M_PI * Ts_symbole);
printf("FFO estimé (Delta f fine) : %f Hz\n", delta_f_fine_est);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= cexp(-I * 2.0 * M_PI * delta_f_fine_est * n / qam->Fs);
}
}
// Phase Offset (PO) Correction
void po(qam_system* qam, double complex* s_with_preamble, double complex* preamble_ref, int L, int total_samples) {
double complex r_preamble[L];
for (int k = 0; k < L; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
r += s_with_preamble[idx] * cexp(-2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs)) / A;
}
r /= qam->N;
r_preamble[k] = r;
}
double complex phase_error_sum = 0;
for (int k = 0; k < L; k++) {
phase_error_sum += r_preamble[k] * conj(preamble_ref[k]);
}
double phi_est = carg(phase_error_sum);
printf("Phase Offset (PO) estimée : %f radians (soit %f degrés)\n", phi_est, phi_est * 180.0 / M_PI);
double complex phase_corr = cexp(-I * phi_est);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= phase_corr;
}
}
// Costas loop
void pll(qam_system* qam, double complex* s_with_preamble, double complex* r_corr, int L, int total_samples, int nb_symbols, double Kp, double Ki, double alpha, FILE* fp_error) {
double phase_est = 0.0;
double integrator = 0.0;
double filtered_error = 0.0;
int start_data_idx = L * qam->N;
int N = qam->N;
for (int k = 0; k < nb_symbols; k++) {
double complex r_k = 0;
for (int n = 0; n < N; n++) {
int idx = start_data_idx + k * N + n;
double t = (double)idx / qam->Fs;
r_k += s_with_preamble[idx] * cexp(-2.0 * I * M_PI * qam->Fc * t) / A;
}
double complex r_symbol = r_k / N;
r_symbol *= cexp(-I * phase_est);
double min_dist = INFINITY;
double complex closest = 0;
for (int idx = 0; idx < qam->M; idx++) {
double dist = cabs(r_symbol - qam->constellation[idx]);
if (dist < min_dist) {
min_dist = dist;
closest = qam->constellation[idx];
}
}
double error = carg(r_symbol * conj(closest));
filtered_error = (1.0 - alpha) * filtered_error + alpha * error;
integrator += Ki * filtered_error;
phase_est += Kp * filtered_error + integrator;
if (fp_error) {
fprintf(fp_error, "%d % .8f\n", k, 100 * filtered_error);
fflush(fp_error);
}
for (int n = 0; n < N; n++) {
int idx = start_data_idx + k * N + n;
r_corr[idx] = s_with_preamble[idx] * cexp(-I * phase_est);
}
}
}
int main () {
// Initialisation du system qam
qam_system qam;
qam.M = 16;
qam.k = (int)log2((double)(qam.M));
qam.sm = (int)sqrt(qam.M);
qam.Fs = 44100;
qam.Ts = 0.01;
qam.N = (int)(qam.Fs * qam.Ts);
qam.Fc = 2000;
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, 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";
int nb_chars = strlen(texte);
int nb_bits = nb_chars * 8;
int nb_symbols = (nb_bits + qam.k - 1) / qam.k;
uint8_t* input_bits = malloc(nb_bits * sizeof(uint8_t));
text_to_bits(nb_chars, nb_bits, texte, input_bits);
// Conversion en symboles
double complex* symbols = malloc(sizeof(double complex) * nb_symbols);
bits_to_symbols(&qam, input_bits, nb_bits, symbols);
// Initialisation du préambule
int L = 15;
int total_samples = qam.N * (nb_symbols + L);
double complex* preamble = malloc(sizeof(double complex) * L);
generate_preamble(&qam, preamble, L);
double complex* preamble_mod = malloc(sizeof(double complex) * L * qam.N);
modulate(&qam, preamble, L, preamble_mod);
double complex* s_mod = malloc(sizeof(double complex) * nb_symbols * qam.N);
modulate(&qam, symbols, nb_symbols, s_mod);
double complex* s = malloc(sizeof(double complex) * total_samples);
double complex* s_with_preamble = concat_preamble_signal(preamble_mod, L, s_mod, nb_symbols, qam.N);
// Ajout du bruit
add_noise(s_with_preamble, total_samples, 20);
FILE *fp_ref = fopen("constellation_ref.dat", "w");
fill_constellation_data(&qam, fp_ref);
fclose(fp_ref);
// Ajout de dephasage
add_dephasage(s_with_preamble, M_PI / 2.0 , total_samples);
// AJout de decalage de fréquence
add_freq(&qam, s_with_preamble, 100, total_samples);
// Estimation / correction du CFO
cfo(s_with_preamble, qam.N, L, qam.Fs, total_samples, qam.Fc);
ffo(&qam, s_with_preamble, preamble, L, total_samples);
// Correction phase
po(&qam, s_with_preamble, preamble, L, total_samples);
// PLL
double complex* s_corrected = malloc(sizeof(double complex) * total_samples);
double Kp = 0.03;
double Ki = 0.002;
double alpha = 0.1;
FILE *fp_pll_error = fopen("pll_error.dat", "w");
pll(&qam, s_with_preamble, s_corrected, L, total_samples, nb_symbols, Kp, Ki, alpha, fp_pll_error);
fclose(fp_pll_error);
// Démodulation
FILE *fp_constel = fopen("constellation.dat", "w");
uint8_t* output_bits = (uint8_t*)malloc(nb_bits * sizeof(uint8_t));
demodulate(&qam, s_corrected + L * qam.N, nb_symbols, output_bits, fp_constel);
fclose(fp_constel);
// Reconstruction du texte
char* texte_recup = malloc(nb_chars + 1);
reconstruction_text(nb_chars, output_bits, texte_recup);
printf("Texte original : %s\n\n", texte);
printf("Texte demodulé : %s\n", texte_recup);
// Calcul du BER
double ber = compare_bits(input_bits, output_bits, nb_bits);
printf("Taux d'erreur blind QAM: %.4f\n", ber * 100);
// Libération mémoire
free(input_bits);
free(output_bits);
free(symbols);
free(preamble);
free(preamble_mod);
free(s_with_preamble);
free(texte_recup);
free_constellation(&qam);
return 0;
}

150
saveQAM/5/debug.py Normal file
View File

@ -0,0 +1,150 @@
#!/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_())

576
saveQAM/5/qam.c Normal file
View File

@ -0,0 +1,576 @@
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
#include <string.h>
#define A 10
struct qam_system_s {
int M; // Nombre de symboles M-QAM
int k; // Nombre de bits/symboles
int sm; // sqrt M
double Fs; // Fréquence d'échantillionage
double Ts; // Temps d'échantillionage
int N; // Nombre d'échantillions
double Fc; // Fréquence de la porteuse
double complex* constellation; // Tableau de symboles I + j Q 1D
};
typedef struct qam_system_s qam_system;
// Initialisation de la constellation (double tableau de taille sqrt(M)),
void init_constellation (qam_system* qam) {
qam->constellation = (double complex*)malloc(sizeof(double complex) * qam->M);
double norm_factor = sqrt((double)(qam->M - 1) / 3.0); // Pour puissance unitaire
for (int i = 0; i < qam->sm; i++) {
double ip = -(qam->sm - 1) + 2 * i;
for (int j = 0; j < qam->sm; j++) {
double qp = -(qam->sm - 1) + 2 * j;
int idx = i * qam->sm + j;
qam->constellation[idx] = (ip + I * qp) / norm_factor;
}
}
}
int bin_to_gray (int x) {
return x ^ (x >> 1);
}
// Iteration de XOR
int gray_to_bin(int g) {
int b = g;
while (g >>= 1)
b ^= g;
return b;
}
// Modulation QAM
void modulate (qam_system* qam, double complex* symbols, int nb_symbols, double complex* s) {
for (int k = 0; k < nb_symbols; k++) {
double complex iq = symbols[k];
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
s[idx] = A * iq * cexp(2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs));
}
}
}
// Demodulation QAM
void demodulate(qam_system* qam, double complex* s, int nb_symbols, uint8_t* bits_hat, FILE *fp_constel) {
for (int k = 0; k < nb_symbols; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
r += s[k * qam->N + n] * cexp(-2 * I * M_PI * qam->Fc * ((double)(k * qam->N + n) / qam->Fs)) / A;
}
r /= qam->N;
if (fp_constel) {
fprintf(fp_constel, "% .8f % .8f\n", creal(r), cimag(r));
fflush(fp_constel);
}
// Distance euclidien de Ir et Qr pour avoir le point le plus proche de la constellation (lent)
double min_d = INFINITY;
int i_cl = 0;
int j_cl = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
i_cl = idx / qam->sm;
j_cl = idx % qam->sm;
}
}
// Gray mapping
if (qam->k % 2 != 0) {
printf("demodulate : k pair (k = %d)\n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
int i_bin = gray_to_bin(i_cl);
int j_bin = gray_to_bin(j_cl);
for (int b = 0; b < bits_per_axis; b++) {
bits_hat[k * qam->k + b] = (i_bin >> (bits_per_axis - 1 - b)) & 1;
bits_hat[k * qam->k + bits_per_axis + b] = (j_bin >> (bits_per_axis - 1 - b)) & 1;
}
}
}
// Calcul du bruit gaussien pour un sigma donné
// Formule de Box-Muller
double gaussian_noise (double sigma) {
double u1 = (rand() + 1) / ((double)RAND_MAX + 2);
double u2 = (rand() + 1) / ((double)RAND_MAX + 2);
return sigma * sqrt(-2 * log(u1)) * cos(2 * M_PI * u2);
}
// Ajout du bruit
void add_noise (double complex* s, int len, double sigma) {
//double signal_power = (2.0/3.0)*(qam.M-1);
//double snr_dB = 5; // SNR en dB
//double snr_lin = pow(10.0, snr_dB / 10.0);
//double sigma = sqrt(signal_power / snr_lin);
for (int i = 0; i < len; i++) {
double nr = gaussian_noise(sigma);
double ni = gaussian_noise(sigma);
s[i] += nr + I * ni;
}
}
// Changer le tableau de bits en boolen ou alors la represenation binaire et shifter pour extraire les bits (pas bien si M plus grand)
void bits_to_symbols (qam_system* qam, uint8_t* bits, int nb_bits, double complex* symbols) {
int nb_symbols = nb_bits / qam->k;
// k pair
if (qam->k % 2 != 0) {
printf("bits_to_symbols : k doit être pair (k = %d) \n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
for (int sym = 0; sym < nb_symbols; sym++) {
// Construire les indices binaires i_bin (MSB...) et j_bin (LSB...)
int i_bin = 0;
int j_bin = 0;
for (int b = 0; b < bits_per_axis; b++) {
i_bin = (i_bin << 1) | bits[sym * qam->k + b];
j_bin = (j_bin << 1) | bits[sym * qam->k + bits_per_axis + b];
}
int i_gray = bin_to_gray(i_bin);
int j_gray = bin_to_gray(j_bin);
if (i_gray < 0 || i_gray >= qam->sm || j_gray < 0 || j_gray >= qam->sm) {
printf("bits_to_symbols : IOOR i_gray = %d j_gray = %d sm = %d \n", i_gray, j_gray, qam->sm);
exit(1);
}
symbols[sym] = qam->constellation[i_gray * qam->sm + j_gray];
}
}
double compare_bits(uint8_t* bits1, uint8_t* bits2, int nb_bits) {
int errors = 0;
for (int i = 0; i < nb_bits; i++) {
if (bits1[i] != bits2[i]) errors++;
}
return (double)errors / nb_bits;
}
void add_dephasage(double complex* s, double phi_offset, int total_samples) {
for (int i = 0; i < total_samples; i++) {
s[i] *= cexp(I * phi_offset);
}
}
void add_freq(qam_system* qam, double complex* s, double freq_offset, int total_samples) {
for (int i = 0; i < total_samples; i++) {
double t = (double)i / qam->Fs;
s[i] *= cexp(I * 2 * M_PI * freq_offset * t);
}
}
void fill_constellation_data(qam_system* qam, FILE *fp_ref) {
for (int i = 0; i < qam->sm; i++) {
for (int j = 0; j < qam->sm; j++) {
int idx = i * qam->sm + j;
fprintf(fp_ref, "% .8f % .8f\n", creal(qam->constellation[idx]), cimag(qam->constellation[idx]));
}
}
}
void reconstruction_text(int nb_chars, uint8_t* output_bits, char* texte_recup) {
for(int i = 0; i < nb_chars; i++){
char c = 0;
for(int b = 0; b < 8; b++){
c |= output_bits[i*8 + b] << (7-b);
}
texte_recup[i] = c;
}
texte_recup[nb_chars] = '\0';
}
void text_to_bits(int nb_chars, int nb_bits, char* texte, uint8_t* input_bits) {
for(int i = 0; i < nb_chars; i++){
for(int b = 0; b < 8; b++){
input_bits[i*8 + b] = (texte[i] >> (7-b)) & 1;
}
}
}
// Libération de la mémoire
void free_constellation(qam_system* qam) {
free(qam->constellation);
}
// Préambule QAM
void generate_preamble(qam_system* qam, double complex* preamble, int L) {
// L 1er symboles de la constellation
for (int i = 0; i < L; i++) {
preamble[i] = qam->constellation[i % qam->M];
}
}
// Concatène le préambule avec le signal modulé
double complex* concat_preamble_signal(double complex* preamble_mod, int preamble_len, double complex* s_mod, int nb_symbols, int N) {
int total_samples = (preamble_len + nb_symbols) * N;
double complex* s_concat = malloc(sizeof(double complex) * total_samples);
// Copier le préambule modulé
for (int i = 0; i < preamble_len * N; i++) {
s_concat[i] = preamble_mod[i];
}
// Copier le signal modulé après le préambule
for (int i = 0; i < nb_symbols * N; i++) {
s_concat[preamble_len * N + i] = s_mod[i];
}
return s_concat;
}
// Coarse Frequency Offset avec préambule avec N = 1 (lag 1)
void cfo(double complex* s_with_preamble, int N, int L, double Fs, int total_samples, double Fc) {
double complex sum = 0;
int lag = 5;
for (int n = lag; n < L * N; n++) {
sum += s_with_preamble[n] * conj(s_with_preamble[n - lag]);
}
double phi = carg(sum);
double f_est = (phi / (2.0 * M_PI * lag)) * Fs;
double f_offset = f_est - Fc;
printf("CFO estimé : %f Hz \n", f_offset);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= cexp(-I * 2.0 * M_PI * f_offset * n / Fs);
}
}
// Fine Frequency Offset (FFO) Correction
void ffo(qam_system* qam, double complex* s_with_preamble, double complex* preamble_ref, int L, int total_samples) {
double complex r_preamble[L];
for (int k = 0; k < L; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
r += s_with_preamble[idx] * cexp(-2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs)) / A;
}
r /= qam->N;
r_preamble[k] = r;
}
double complex diff_phase_sum = 0;
double Ts_symbole = qam->N / qam->Fs;
for (int k = 1; k < L; k++) {
double complex error_k = r_preamble[k] * conj(preamble_ref[k]);
double complex error_k_minus_1 = r_preamble[k - 1] * conj(preamble_ref[k - 1]);
diff_phase_sum += error_k * conj(error_k_minus_1);
}
double phi_sym = carg(diff_phase_sum);
double delta_f_fine_est = phi_sym / (2.0 * M_PI * Ts_symbole);
printf("FFO estimé (Delta f fine) : %f Hz\n", delta_f_fine_est);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= cexp(-I * 2.0 * M_PI * delta_f_fine_est * n / qam->Fs);
}
}
// Phase Offset (PO) Correction
void po(qam_system* qam, double complex* s_with_preamble, double complex* preamble_ref, int L, int total_samples) {
double complex r_preamble[L];
for (int k = 0; k < L; k++) {
double complex r = 0;
for (int n = 0; n < qam->N; n++) {
int idx = k * qam->N + n;
r += s_with_preamble[idx] * cexp(-2 * I * M_PI * qam->Fc * ((double)idx / qam->Fs)) / A;
}
r /= qam->N;
r_preamble[k] = r;
}
double complex phase_error_sum = 0;
for (int k = 0; k < L; k++) {
phase_error_sum += r_preamble[k] * conj(preamble_ref[k]);
}
double phi_est = carg(phase_error_sum);
printf("Phase Offset (PO) estimée : %f radians (soit %f degrés)\n", phi_est, phi_est * 180.0 / M_PI);
double complex phase_corr = cexp(-I * phi_est);
for (int n = 0; n < total_samples; n++) {
s_with_preamble[n] *= phase_corr;
}
}
// Costas loop
void pll(qam_system* qam, double complex* s_with_preamble, double complex* r_corr, int L, int total_samples, int nb_symbols, double Kp, double Ki, double alpha, FILE* fp_error) {
double phase_est = 0.0;
double integrator = 0.0;
double filtered_error = 0.0;
int start_data_idx = L * qam->N;
int N = qam->N;
for (int k = 0; k < nb_symbols; k++) {
double complex r_k = 0;
for (int n = 0; n < N; n++) {
int idx = start_data_idx + k * N + n;
double t = (double)idx / qam->Fs;
r_k += s_with_preamble[idx] * cexp(-2.0 * I * M_PI * qam->Fc * t) / A;
}
double complex r_symbol = r_k / N;
r_symbol *= cexp(-I * phase_est);
double min_dist = INFINITY;
double complex closest = 0;
for (int idx = 0; idx < qam->M; idx++) {
double dist = cabs(r_symbol - qam->constellation[idx]);
if (dist < min_dist) {
min_dist = dist;
closest = qam->constellation[idx];
}
}
double error = carg(r_symbol * conj(closest));
filtered_error = (1.0 - alpha) * filtered_error + alpha * error;
integrator += Ki * filtered_error;
phase_est += Kp * filtered_error + integrator;
if (fp_error) {
fprintf(fp_error, "%d % .8f\n", k, 100 * filtered_error);
fflush(fp_error);
}
for (int n = 0; n < N; n++) {
int idx = start_data_idx + k * N + n;
r_corr[idx] = s_with_preamble[idx] * cexp(-I * phase_est);
}
}
}
// 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, FILE* fp_error) {
double integrator = 0.0;
double timing_offset = 0.0;
double current_idx_double = 0.0;
double complex d_k_minus_1 = 0;
for (int k = 0; k < nb_symbols; k++) {
int opt_idx = (int)round(current_idx_double);
int tilde_idx = (int)round(current_idx_double + qam->N / 2.0);
if (opt_idx >= nb_symbols * qam->N || tilde_idx >= nb_symbols * qam->N || tilde_idx < 0)
break;
double complex r_k = s_data[opt_idx];
double complex r_tilde_k = s_data[tilde_idx];
double min_d = INFINITY;
double complex d_k = 0;
int decision_idx = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r_k - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
d_k = qam->constellation[idx];
decision_idx = idx;
}
}
r_mm_out[k] = r_k;
// Erreur M&M
double error_k = 0.0;
if (k > 0) {
// Formule M&M: e_k = Re{ r_tilde_k * (d_{k-1}^* - d_k^*) }
double complex error_term = r_tilde_k * conj(d_k_minus_1 - d_k);
error_k = creal(error_term);
}
// Costas Loop PI
integrator += Ki_mm * error_k;
timing_offset = Kp_mm * error_k + integrator;
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);
}
}
}
// Demodulation M&M
void demodulate_sync_adapted(qam_system* qam, double complex* r_mm_out, int nb_symbols, uint8_t* bits_hat, FILE *fp_constel) {
for (int k = 0; k < nb_symbols; k++) {
double complex r = r_mm_out[k];
if (fp_constel) {
fprintf(fp_constel, "% .8f % .8f\n", creal(r), cimag(r));
fflush(fp_constel);
}
// Distance euclidien (quantification/décision)
double min_d = INFINITY;
int i_cl = 0;
int j_cl = 0;
for (int idx = 0; idx < qam->M; idx++) {
double d = cabs(r - qam->constellation[idx]);
if (d < min_d) {
min_d = d;
i_cl = idx / qam->sm;
j_cl = idx % qam->sm;
}
}
// Gray mapping et extraction des bits
if (qam->k % 2 != 0) {
printf("demodulate : k doit être pair (k = %d)\n", qam->k);
exit(1);
}
int bits_per_axis = qam->k / 2;
int i_bin = gray_to_bin(i_cl);
int j_bin = gray_to_bin(j_cl);
for (int b = 0; b < bits_per_axis; b++) {
bits_hat[k * qam->k + b] = (i_bin >> (bits_per_axis - 1 - b)) & 1;
bits_hat[k * qam->k + bits_per_axis + b] = (j_bin >> (bits_per_axis - 1 - b)) & 1;
}
}
}
void demodulate_carrier(qam_system* qam, double complex* s_input, double complex* s_bandebase, int total_samples) {
for (int n = 0; n < total_samples; n++) {
double t = (double)n / qam->Fs;
s_bandebase[n] = s_input[n] * cexp(-I * 2 * M_PI * qam->Fc * t) / A;
}
}
int main () {
// Initialisation du system qam
qam_system qam;
qam.M = 16;
qam.k = (int)log2((double)(qam.M));
qam.sm = (int)sqrt(qam.M);
qam.Fs = 44100;
qam.Ts = 0.01;
qam.N = (int)(qam.Fs * qam.Ts);
qam.Fc = 2000;
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, 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;
uint8_t* input_bits = malloc(nb_bits * sizeof(uint8_t));
text_to_bits(nb_chars, nb_bits, texte, input_bits);
// Conversion en symboles
double complex* symbols = malloc(sizeof(double complex) * nb_symbols);
bits_to_symbols(&qam, input_bits, nb_bits, symbols);
// Initialisation du préambule
int L = 15;
int total_samples = qam.N * (nb_symbols + L);
double complex* preamble = malloc(sizeof(double complex) * L);
generate_preamble(&qam, preamble, L);
double complex* preamble_mod = malloc(sizeof(double complex) * L * qam.N);
modulate(&qam, preamble, L, preamble_mod);
double complex* s_mod = malloc(sizeof(double complex) * nb_symbols * qam.N);
modulate(&qam, symbols, nb_symbols, s_mod);
double complex* s = malloc(sizeof(double complex) * total_samples);
double complex* s_with_preamble = concat_preamble_signal(preamble_mod, L, s_mod, nb_symbols, qam.N);
// Ajout du bruit
add_noise(s_with_preamble, total_samples, 2);
FILE *fp_ref = fopen("constellation_ref.dat", "w");
fill_constellation_data(&qam, fp_ref);
fclose(fp_ref);
// Ajout de dephasage
add_dephasage(s_with_preamble, M_PI / 2.0 , total_samples);
// AJout de decalage de fréquence
add_freq(&qam, s_with_preamble, 100, total_samples);
// Estimation / correction du CFO
cfo(s_with_preamble, qam.N, L, qam.Fs, total_samples, qam.Fc);
ffo(&qam, s_with_preamble, preamble, L, total_samples);
// Correction phase
po(&qam, s_with_preamble, preamble, L, total_samples);
// PLL
double complex* s_corrected = malloc(sizeof(double complex) * total_samples);
double Kp = 0.03;
double Ki = 0.002;
double alpha = 0.1;
FILE *fp_pll_error = fopen("pll_error.dat", "w");
pll(&qam, s_with_preamble, s_corrected, L, total_samples, nb_symbols, Kp, Ki, alpha, fp_pll_error);
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, nb_symbols * qam.N);
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");
uint8_t* output_bits = (uint8_t*)malloc(nb_bits * sizeof(uint8_t));
demodulate_sync_adapted(&qam, r_mm_out, nb_symbols, output_bits, fp_constel);
//demodulate(&qam, s_corrected + L * qam.N, nb_symbols, output_bits, fp_constel);
fclose(fp_constel);
// Reconstruction du texte
char* texte_recup = malloc(nb_chars + 1);
reconstruction_text(nb_chars, output_bits, texte_recup);
//printf("Texte original : %s\n\n", texte);
printf("Texte demodulé : %s\n", texte_recup);
// Calcul du BER
double ber = compare_bits(input_bits, output_bits, nb_bits);
printf("Taux d'erreur QAM: %.4f\n", ber * 100);
// Libération mémoire
free(input_bits);
free(output_bits);
free(symbols);
free(preamble);
free(preamble_mod);
free(s_with_preamble);
free(texte_recup);
free_constellation(&qam);
return 0;
}