Compare commits
21 Commits
cffea6d754
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 367eeb5847 | |||
| ae04d5e2bd | |||
| 63f706eee5 | |||
| d5e0e4129b | |||
| 132a8f903f | |||
| 3b583de596 | |||
| 144cbbc5c3 | |||
| e591d98772 | |||
| 526327f313 | |||
| 1b3e861181 | |||
| d76ecd4660 | |||
| 809d1a726d | |||
| 880d6cdfac | |||
| 43238e304e | |||
| 0118505523 | |||
| 5f353f005b | |||
| ddfe1bbd90 | |||
| 98ccdf857f | |||
| bf8421abb6 | |||
| 9b652e9338 | |||
| c46136b803 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
|||||||
*.txt
|
*.txt
|
||||||
*.png
|
*.png
|
||||||
*.jpg
|
*.jpg
|
||||||
|
*.gif
|
||||||
|
*.dat
|
||||||
|
|||||||
68
QAM/c.py
Normal file
68
QAM/c.py
Normal 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()
|
||||||
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
112
QAM/debug.py
112
QAM/debug.py
@ -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)
|
||||||
|
|||||||
356
QAM/qam.c
356
QAM/qam.c
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
157
QAM/todo.md
157
QAM/todo.md
@ -1,157 +0,0 @@
|
|||||||
|
|
||||||
# 🧭 Ordre d’implémentation d’une 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 l’instant exact d’échantillonnage par symbole.
|
|
||||||
|
|
||||||
**Tu connais déjà :**
|
|
||||||
|
|
||||||
* Interpolateur fractionnaire
|
|
||||||
* Détecteur d’erreur M&M
|
|
||||||
* Boucle PI
|
|
||||||
|
|
||||||
💡 Astuce : commence avec un signal *parfaitement corrigé en fréquence* avant d’activer 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 l’ISI 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 d’abord 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 d’erreurs / décodage (FEC)**
|
|
||||||
|
|
||||||
🎯 Objectif : terminer la chaîne par le décodage des bits.
|
|
||||||
|
|
||||||
* LDPC, Viterbi, Turbo selon ton système
|
|
||||||
* C’est 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 d’implé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 qu’il faut coder / comprendre | Test / Validation |
|
|
||||||
| - | --------------------------------------- | ----------------------------------------------- | ---------- | ----------------------------------------------- | ------------------------------------------------ |
|
|
||||||
| 0 | **Acquisition & Filtrage RRC** | Lire le signal IQ et filtrer pour limiter l’ISI | 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 d’inté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 l’instabilité
|
|
||||||
* Commencer avec **trames simples** avant bruit réel, puis ajouter AWGN / jitter / offsets
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
@ -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
104
saveQAM/3/debug.py
Normal 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
278
saveQAM/3/qam.c
Normal 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
68
saveQAM/4/c.py
Normal 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
104
saveQAM/4/debug.py
Normal 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
463
saveQAM/4/qam.c
Normal 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
150
saveQAM/5/debug.py
Normal 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
576
saveQAM/5/qam.c
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user