%% OFDM: Implementación básica de transmisor y receptor
% Copyright (c) 2023 G.T.A.S. (Grupo Tratamiento Avanzado de Señal). Universidad de Cantabria.
%
% Ejemplo de cómo se implenta un transmisor y un receptor básico de OFDM, incluyendo el uso de IFFT/FFT y la inclusión de la extensión cíclica.
%
%% Parámetros del transmisor
% Primero definimos los parámetros de la señal OFDM transmitida. Puedes modificarlos si deseas probar con más o menos subpoortadoras o con otro tipo de modulación. En un principio, te recomiendo emplear pocas suportadoras, por ejemplo N=4, y generar pocos símbolos OFDM, N_s=5, para poder observar en la línea de comandos las matrices generadas.

N = 4;     % Número de subportadoras (tamaño FFT)
Ns = 5;    % Número de símbolos OFDM a generar
M = 4;     % Tipo de modulación (log2(M) bits/simbolo): BPSK M=2; QPSK M=4; 16QAM M=16; 64QAM M=64

% Indica el número de muestras, N_g, del tiempo de guarda. En principio, por simplicidad, te aconsejo utilizar N_g=0
% .

Ng = 0;    % Número de muestras del tiempo de guarda cíclica

% El factor de sobremuestreo, L
% , permite controlar el sobremuestreo de la señal OFDM generada por el transmisor. Si lo cambias, cada símbolo OFDM tendrá NL muestras en lugar de N. Utiliza L=1 en los primeros ejemplos.

L = 1;     % Factor de sobremuestreo

%
%% Transmisor
% Generamos la señal OFDM transmitida:

bits = randi([0 1],N*log2(M),Ns);       % bits a transmitir. Cada columna corresponde a un símbolo OFDM
s = qammod(bits,M,'InputType', 'Bit');  % símbolos M-QAM a transmitir
if L==1   % Sin sobremuestreo
    x = ifft(s);             % Matriz de muestras temporales de la señal OFDM. Cada columna corresponde a un símbolo OFDM 
else      % Con sobremuestreo
    x = L*ifft([s(1:N/2,:);zeros(N*L-N,Ns);s(N/2+1:end,:)],L*N); % Matriz de muestras temporales sobremuestreadas de la señal OFDM. Cada columna corresponde a un símbolo OFDM     
end
xg = [x(end-Ng*L+1:end,:);x];  % Se añade Tiempo de Guarda con extensión cíclica
xt = xg(:);                    % Vector con las muestras de la señal OFDM transmitida

% Vamos a observar las matrices que se han generado en el transmisor.
% Empezamos por los bits que se transmiten. Cada columna contiene los N\log_2(M)bits de cada uno de los N_s símbolos OFDM:

disp(bits)

% La matriz de símbolos transmitidos contiene en cada columna los N símbolos BPSK, QPSK o M-QAM (según hayas elegido) de cada símbolo OFDM:

disp(s)

% También observa la salida de la IFFT. Cada columna representa las N muestras (o NL muestras si has utilizado sobremuestreo) de la señal en el dominio del tiempo que tiene por FFT la correspondiente columna de símbolos:

disp(x)

% Añadir la extensión cíclica es fácil: simplemente se han copiado las últimas N_g filas encima. Puedes observarlo en la matriz xg

disp(xg)

% Finalmente, la señal transmitida (el equivalente paso bajo) no es más que las muestras de cada símbolo OFDM (cada columna) colocadas una detrás de otra

xt.'

% Observa la señal OFDM. Como se trata de una señal equivalente paso bajo, podemos representar la señal I (parte real), la señal Q (parte imaginaria), su módulo o su fase:

if length(xt)<200 tipo_linea = '-o'; else tipo_linea = '-'; end
subplot(311);plot(real(xt),tipo_linea);xlabel('n');set(gca,'XTick',(0:Ns)*L*(N+Ng));grid on
line([0 length(xt)],[0 0],'Color','k')
legend('Señal I');xlabel('muestras');ylabel('amplitud');title('Señal OFDM transmitida')
subplot(312);plot(imag(xt),tipo_linea);set(gca,'XTick',(0:Ns)*L*(N+Ng));grid on
line([0 length(xt)],[0 0],'Color','k')
legend('Señal Q');xlabel('muestras');ylabel('amplitud');
subplot(313);plot(abs(xt),tipo_linea);set(gca,'XTick',(0:Ns)*L*(N+Ng));grid on
legend('Envolvente');xlabel('muestras');ylabel('amplitud');

%
%% RECEPTOR
% En el receptor se llevan a cabo los procesos inversos a los del transmisor:

yt = xt(1:L:end);                % Diezmado por un factor L si es preciso
y = reshape(yt,N+Ng,Ns);         % Reorganización en forma matricial. Cada columna corresponde a las N+Ng muestras de un símbolo OFDM
y = y(Ng+1:end,:);               % Eliminación de Guardas
z = fft(y,N);                    % FFT para obtener los símbolos de cada subportadora
b = qamdemod(z,M,'OutputType','Bit')

% Puedes comprobar la matriz de observables (símbolos recibidos). Deberían coincidir con la matriz s de símbolos transmitidos.

z

% También podemos representar gráficamente dichos observables, es decir, la constelación recibida:

subplot(111);plot(z(:),'.','MarkerSize',16);title('Constelación Recibida')
escala = sqrt(M);axis(escala*[-1 1 -1 1]);axis square;grid on

%
%% Prueba tú ahora
% Prueba a modificar los parámetros de la señal OFDM y ejecuta de nuevo el Live Script. Te propongo los siguientes ejemplos, aunque tienes libertad para probar por tu cuenta:
% Si aumentas el factor de sobremuestreo, usando por ejemplo L=10, podrás observar la señal transmitida más suave al estar sobremuestreadas. Ahora cada símbolo OFDM tiene (dura) LNmuestras.
%                  N=4; N_s=5; M=4; N_g=0; L=10
% Prueba a aumentar el número de subportadoras, por ejemplo M=16. En esencia nada cambia, simplemente cada símbolo es más largo que en el primer ejemplo y contiene más información, en concreto N\log_2(M)=16\log_2(4)=32 bits.
%                  N=16; N_s=5; M=4; N_g=0; L=1
% Prueba a añadir el tiempo de guarda. Por ejemplo, vuelve a trabajar con N=4 subportadoras y sin sobremuestreo (L=1). Si haces Ng=2 comprobarás que cada símbolo OFDM pasa a tener 2 muestras más, que coinciden con las dós últimas muestras de dicho símbolo (extensión cíclica).
%                  N=4; N_s=5; M=4; N_g=2; L=1
% Si aumentas el factor de sobremuestreo, usando por ejemplo L=10, podrás observar la señal transmitida más suave al estar sobremuestreadas. Te resultará sencillo identificar la extensión cíclica de cada símbolo: cuidado, ahora dicha estensión cíclica dura LN_g muestras y cada símbolo OFDM completo dura LN+LN_g=L(N+Ng).
%                  N=4; N_s=5; M=4; N_g=2; L=10
% Prueba a aumentar el número de subportadoras. Con los parámetros que te propongo a continuación es fácil observar en la primera figura (Señal OFDM transmitida) cómo las N_g=4 muestras al inicio del símbolo OFDM son idénticas a las últimas
%                  N=16; N_s=5; M=4; N_g=4; L=1
% Prueba a cambiar la modulación, por ejemplo haciendo M=16 para el caso 16QAM (\log_2(M)=4bits por cada símbolo 16QAM).
%                  N=16; N_s=5; M=16; N_g=4; L=1