466 lines
15 KiB
C
466 lines
15 KiB
C
#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";
|
|
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 = 16;
|
|
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, 24);
|
|
|
|
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 / 6.0 , total_samples);
|
|
|
|
// AJout de decalage de fréquence
|
|
add_freq(&qam, s_with_preamble, 50, 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.5;
|
|
double Ki = 0.01;
|
|
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;
|
|
}
|