← Índice del material Práctica 2 — Conversión A/D y D/A

Práctica 2: Conversión A/D y D/A

Señales y Sistemas Lineales

1 Introducción

En esta práctica recorremos con MATLAB la cadena completa de digitalización de una señal: partimos de una señal analógica, la muestreamos, la cuantizamos y finalmente la reconstruimos para volver al mundo continuo. Son los tres bloques que forman cualquier conversor A/D y D/A real.

Damos por supuesto que ya has hecho las prácticas 0 y 1, así que manejas variables, vectores, operaciones elemento a elemento y los comandos básicos de representación gráfica. Aquí los aplicaremos a un problema concreto de la asignatura. Las funciones propias en archivos .m, que en esta práctica usarás a menudo, se explican en el trabajo previo.

Como en la práctica anterior, el documento está pensado para que lo sigas escribiendo tú mismo el código en un script: no basta con leerlo, tienes que reproducir cada ejercicio y experimentar cambiando valores.

1.1 Objetivos

Al terminar deberías ser capaz de:

  • Distinguir entre una señal “continua” (un vector de tiempos muy denso) y una señal discreta (las muestras tomadas cada \(T_s = 1/f_s\) segundos), y representar cada una con el comando adecuado: plot o stem.
  • Reconocer el fenómeno del aliasing, explicar por qué dos señales distintas pueden dar las mismas muestras, aplicar el criterio de Nyquist (\(f_s > 2f_{\max}\)) para evitarlo y calcular la frecuencia del alias a partir de la frecuencia digital.
  • Cuantizar una señal con un número dado de bits, distinguir el redondeo del truncamiento y medir la degradación que introduce mediante el error de cuantización y la relación señal-ruido de cuantización (SQNR), relacionándola con el número de bits.
  • Reconstruir una señal a partir de sus muestras con la interpolación ideal (sinc), entender en qué se diferencia del retenedor de orden cero (ZOH) y comprobar cuándo deja de funcionar, como predice el teorema de muestreo de Nyquist-Shannon.

1.2 Estructura de la práctica

El documento se divide en dos partes:

  • El trabajo previo debes prepararlo antes de acudir al laboratorio. Repasa la teoría que vas a necesitar, presenta las funciones que se proporcionan y propone unos ejercicios breves. Cada ejercicio va acompañado de su solución, que puedes desplegar para comprobar tu respuesta. Intenta resolverlos por tu cuenta antes de mirarla.
  • El trabajo de laboratorio es lo que realizarás durante la sesión. En él escribirás tu propia función, calcula_sqnr, y usarás las que se proporcionan en el trabajo previo, cuant_redondeo e interp_ideal: guárdalas en tu carpeta de trabajo, cada una en su archivo .m. Si llegas con el trabajo previo hecho, la sesión te cundirá mucho más.
ImportanteFlujo de trabajo con MATLAB

Debes seguir las siguientes pautas:

  • Trabaja en una carpeta dedicada solo para esta práctica

  • Asegúrate de que coincide con el directorio de trabajo de MATLAB

  • Usa scripts de MATLAB (ficheros .m). No uses Live Scripts (.mlx).

  • Crea un script ejercicioX.m individual por cada ejercicio.

  • Siempre ejecuta el script completo (botón “Run” o F5).

AdvertenciaTrabajo previo y cuestionario

Recuerda completar el cuestionario en Aula Virtual antes de acudir al laboratorio, cuando termines el trabajo previo. El cuestionario no tiene límite de tiempo. Puedes tenerlo abierto mientras trabajas en los ejercicios. El cuestionario es evaluable y constituye el 20% de la nota de evaluación continua de prácticas.

AdvertenciaTrabajo de laboratorio y entrega

Debes completar el trabajo de laboratorio durante la sesión y entregarlo al final. La entrega es obligatoria y se hace en Aula Virtual, subiendo a la tarea correspondiente un único archivo comprimido .zip que contenga todos tus ficheros .m. Solamente es necesaria una entrega por pareja. La nota máxima a la que puedes optar depende del número de ejercicios que hayas completado y entregado correctamente:

Fichero Ejercicio Nota máxima
ejercicio5.m Aliasing 5
calcula_sqnr.m Función para calcular SQNR 6
ejercicio7.m Cuantización y SQNR 8
ejercicio8.m Reconstrucción ideal 10
AdvertenciaEvaluación de los ejercicios

El/la profesor/a evaluará los ejercicios de la práctica mediante preguntas orales durante la sesión. Asegúrate de comprender tu código y los resultados que obtienes, porque tendrás que responder a preguntas sobre ellos. Las preguntas son individuales aunque trabajéis en parejas. Cuando completes un ejercicio, pide al/a la profesor/a que te lo evalúe.

2 Trabajo previo

2.1 Señales “continuas”

Cuando usamos el comando plot con un vector de tiempos muy denso parece que dibujamos una curva continua y suave. Pero MATLAB en realidad solo conoce los valores de la señal en los puntos del vector t, y une esos puntos con segmentos rectos. La “continuidad” es una ilusión que mejora cuanto más puntos usamos.

Veámoslo dibujando la misma señal senoidal con un número creciente de puntos:

Tip✏️ Ejercicio 1

Dibuja una señal sin de frecuencia 1 Hz y amplitud unitaria en el intervalo [0, 1] s

  • usa dos números de puntos distintos, \(N = 5\) y \(N = 50\), para generar el vector de tiempos

  • dibuja cada caso en un panel de una figura 2 x 1 subplot(2, 1, ...)

  • utiliza un trazo del tipo '-o'.

Observa cómo cambia la apariencia de la curva al pasar de N = 5 a N = 50.

Plantilla de código, completa los ???:

clear all; close all; clc;

% --- Panel 1: N = 5 puntos ---
N = ???;
t = linspace(???,???,???);         % N puntos entre 0 y 1
x = ???;                           % seno de amplitud unitaria a 1 Hz

subplot(2, 1, 1)                   % 2 filas, 1 columna, dibujamos en el panel 1
plot(t, x, '-o', 'MarkerSize', 2)
title('N = 5 puntos')
xlabel('t (s)');
ylabel('x(t)')
grid on

% --- Panel 2: N = 50 puntos ---
N = ???;
t = linspace(???,???,???);
x = ???;                          % seno de amplitud unitaria a 1 Hz

subplot(???,???,???)              % 2 filas, 1 columna, dibujamos en el panel 2
plot(t, x, '-o', 'MarkerSize', 2)
title('N = 50 puntos')
xlabel('t (s)');
ylabel('x(t)')
grid on

Deberías obtener la siguiente figura. Fíjate en que lo único que cambia de un panel a otro es el número de puntos N que usamos para generar el vector de tiempos t.

2.2 Muestreo (señales discretas)

Cuando hablamos de “muestrear” en MATLAB nos referimos simplemente a quedarnos con algunos de los puntos de la señal continua. Por ejemplo, si queremos muestrear una señal a 5 Hz (o sea, cada 0.2 s), tomamos solo sus valores en los instantes \(t = \{0, 0.2, 0.4, 0.6, 0.8, 1\}\) s.

Comprueba que el siguiente código genera dicho vector de tiempos:

fs = 5;          % frecuencia de muestreo (Hz)
Ts = 1/fs;       % periodo de muestreo (s)
t = 0:Ts:1;        % instantes de muestreo
NotaVectores de tiempo: linspace vs. operador :

Ambas formas nos permiten generar vectores de tiempo, pero normalmente las usamos con propósitos distintos:

  • Para muestrear señales es más cómodo usar t1:Ts:t2, lo cual genera un vector de tiempos desde t1 hasta t2 con periodo de muestreo Ts.

  • Sin embargo, si nos piden dibujar una señal “continua” con N puntos, entonces sí conviene usar linspace(t1, t2, N).

Recuerda que si muestreamos una señal continua de frecuencia \(f\) Hz a una frecuencia de muestreo \(f_s\) Hz, entonces la señal discreta resultante tiene una frecuencia digital:

\[ \Omega = \pi \frac{f}{f_{nyq}} \quad \text{rad/muestra} \]

donde \(f_{nyq} = f_s/2\) es la frecuencia de Nyquist.

Tip✏️ Ejercicio 2

Dibuja:

  • una señal sin “continua” de amplitud 3 y frecuencia 2 Hz con 1000 puntos en el intervalo [0, 1] s, con plot y línea continua azul b-

  • de forma superpuesta, la misma señal muestreada a 5 Hz, con stem en color rojo r

Plantilla de código, completa los ???:

clear all; close all; clc;
f  = ???;                      % frecuencia de la señal (Hz)
fs = ???;                      % frecuencia de muestreo (Hz)
Ts = ???;                   % periodo de muestreo: 0.2 s

tc = linspace(???,???,???);   % tiempo "continuo": de 0 a 1 s con 1000 puntos
xc = ???;         % señal continua

tn = ???;                 % instantes de muestreo: de 0 a 1 cada Ts segundos
xn = ???;         % muestras de la señal

plot(tc, xc, 'b-', 'LineWidth', 1.5)          % señal continua
hold on
stem(tn, xn, 'r', 'filled', 'LineWidth', 1.2) % sus muestras
hold off
xlabel('t (s)'); ylabel('x(t)')
title('Senoide de 2 Hz y sus muestras a f_s = 5 Hz')
legend('x(t) continua', 'x[n] muestras')
grid on

Deberías obtener la siguiente figura. Fíjate en que las dos señales se construyen con la misma fórmula: lo único que cambia es el vector de tiempos y la forma de dibujarlas.

2.3 Funciones en MATLAB

Las funciones son bloques de código que reciben entradas y devuelven salidas:

  • las usamos para reutilizar código y organizarlo mejor.

  • se definen en un archivo .m independiente

  • el archivo debe tener el mismo nombre que la función

  • el archivo debe estar dentro del directorio de trabajo

  • se pueden llamar desde cualquier script y también desde la línea de comandos.

Aquí tienes un ejemplo de una función que calcula la potencia de una señal discreta:

function p = potencia(x)
% POTENCIA  Calcula la potencia de una señal discreta.

p = sum(x.^2) / length(x);
end
Tip✏️ Ejercicio 3

1) Crea en tu carpeta de trabajo un archivo potencia.m con el código anterior.

2) Comprueba que funciona, simplemente ejecutando en la línea de comandos:

p = potencia([1 2 3])

Deberías obtener p = 4.6667.

2.4 Redondeo y truncamiento

Las operaciones de redondeo y truncamiento son habituales en computación y procesamiento de señales. MATLAB dispone de las funciones:

  • round(x): aproxima x al entero más cercano
  • floor(x): aproxima x al entero más próximo por debajo

En ambos casos, se pueden aplicar directamente a un vector, y la operación se realiza elemento a elemento. En ese caso, devuelven un vector de la misma dimensión que el original, solamente con valores enteros.

Tip✏️ Ejercicio 4

Usando simplemente la línea de comandos, aplica las funciones round y floor al vector

x = [-1.7, -0.4, 0.2, 1.3, 2.8]

y comprueba si los resultados son coherentes.

Para el caso de redondeo, deberías obtener:

round([-1.7, -0.4, 0.2, 1.3, 2.8]) = 
   -2   0    0    1    3

Para el caso de truncamiento, deberías obtener:

floor([-1.7, -0.4, 0.2, 1.3, 2.8]) = 
   -2   -1    0    1    2 

2.5 Aliasing

Si muestreamos demasiado despacio, dos señales continuas de frecuencias distintas pueden producir exactamente las mismas muestras. Al mirar solamente los puntos muestreados, se vuelven indistinguibles.

Para que las muestras representen sin ambigüedad una señal, la frecuencia de muestreo debe ser mayor que el doble de la frecuencia máxima de la señal:

\[ f_s > 2\,f_{\max} \]

Dicho de otra manera, \(f_{\max} < f_s/2 =f_{nyq}\), la frecuencia máxima de la señal debe ser menor que la frecuencia de Nyquist.

AdvertenciaAliasing

El efecto de aliasing aparece cuando no se cumple el criterio de Nyquist, es decir, cuando la señal tiene componentes de frecuencia mayor que la frecuencia de Nyquist \(f_{nyq} = f_s/2\). Como resultado, el muestreo da lugar a una frecuencia “fantasma” (alias) que no es real.

Si se produce aliasing y reconstruimos la señal a partir de sus muestras, no recuperaremos la señal original, sino un alias de la misma. Recuerda que la frecuencia digital equivalente \(\Omega'\) se obtiene restando múltiplos de \(2\pi\) a \(\Omega\) hasta que quede dentro del rango \([-\pi, \pi]\):

\[ \Omega' = \Omega - 2\pi k, \qquad k \in \mathbb{Z} \]

La frecuencia de la señal reconstruida (el alias) es entonces:

\[ f' = \frac{|\Omega'|}{\pi} f_{nyq} \]

\(\Omega'\) puede salir negativa, pero la frecuencia \(f'\) se expresa siempre como positiva; por eso se toma el valor absoluto.

2.6 Cuantización

Se ha visto en teoría que uno de los pasos en la digitalización de una señal es la cuantización, que consiste en aproximar cada valor de la señal a un nivel discreto dentro de un rango determinado.

Se proporciona la siguiente función, que permite cuantizar una señal en el rango [Amin, Amax] por redondeo al nivel más cercano:

function xq = cuant_redondeo(x, Amin, Amax, N)
% CUANT_REDONDEO  Cuantización uniforme por redondeo al nivel más cercano.
%   xq = CUANT_REDONDEO(x, Amin, Amax, N) cuantiza x en el rango [Amin, Amax]
%   con N bits (2^N niveles), redondeando al nivel más cercano.
    
    L     = 2^N;
    Delta = (Amax - Amin) / (L - 1);
    xq    = Amin + Delta * round((x - Amin)/Delta);
    xq    = min(max(xq, Amin), Amax);
end

2.7 Error de cuantización y SQNR

Tanto si se utiliza redondeo como truncamiento, la señal cuantizada será diferente de la original y por tanto se introduce un error de cuantización. Este error puede medirse de varias formas, siendo una de las más comunes la relación señal-ruido de cuantización (SQNR), que compara la potencia de la señal original (\(x\)) con la potencia del error de cuantización (\(e\)): \[ SQNR = 10 \log_{10} \left( \frac{\sum x[n]^2}{\sum e[n]^2} \right)\, \text{dB}\]

siendo \[ e[n] = x_q[n] - x[n] \] el error entre la señal cuantizada y la original.

2.8 Reconstrucción D/A

Generalmente muestreamos una señal para almacenarla o procesarla digitalmente, y luego la reconstruimos para escucharla o visualizarla. Una señal discreta solamente existe en los instantes de muestreo, y para poder generar una señal continua necesitamos interpolar entre las muestras.

Existe un interpolador trivial, conocido como retenedor de orden cero, comúnmente ZOH (por sus iniciales en inglés, zero-order hold), que consiste en mantener cada muestra constante a lo largo del tiempo hasta la siguiente muestra. Tienes un ejemplo en la siguiente figura:

  • La señal original es una senoide de 1 Hz (azul)

  • La señal muestreada a 10 Hz da lugar a la senoide discreta (rojo)

  • La reconstrucción ZOH a partir de las muestras consiste en mantener el último valor constante (verde)

El ZOH es el interpolador más sencillo posible. Sin embargo, existe un interpolador ideal, capaz de reconstruir la señal original a partir de sus muestras, si se cumplen unas condiciones:

NotaTeorema de Muestreo de Nyquist-Shannon

Si una señal no contiene frecuencias mayores que \(f_{\max}\), entonces puede ser perfectamente reconstruida a partir de sus muestras si la frecuencia de muestreo \(f_s\) cumple \[ f_s > 2 f_{\max} \] mediante la siguiente fórmula de interpolación: \[ x(t) = \sum_{n=-\infty}^{\infty} x[n] \cdot \text{sinc}\left(\frac{t - nT_s}{T_s}\right) \] donde \(\text{sinc}(x) = \frac{\sin(\pi x)}{\pi x}\) y \(T_s = 1/f_s\) es el periodo de muestreo.

Se proporciona la siguiente función, que implementa la fórmula de la interpolación ideal:

function xc_ideal = interp_ideal(xn, tn, tc)
% INTERP_IDEAL  Interpolación ideal mediante sinc.
%   xn : valores de la señal discreta
%   tn : instantes de tiempo de cada muestra (mismo tamaño que xn)
%   tc : instantes de tiempo "continuos" donde queremos interpolar la señal
    
    Ts = tn(2) - tn(1);  % periodo de muestreo
    
    xc_ideal = zeros(1, length(tc));  % inicializamos el vector de salida
    
    for k = 1:length(tc)

        u = (tc(k) - tn)/Ts;            % argumento de la sinc para cada muestra
        s = sin(pi*u)./(pi*u);          % sinc(u) = sin(pi u)/(pi u)
        s(u == 0) = 1;                  % sinc(0) = 1 (evita 0/0)
        xc_ideal(k) = sum(xn .* s);     % suma de las muestras ponderadas
        
    end
end

3 Trabajo de laboratorio

ImportanteFlujo de trabajo con MATLAB

Debes seguir las siguientes pautas:

  • Trabaja en una carpeta dedicada solo para esta práctica

  • Asegúrate de que coincide con el directorio de trabajo de MATLAB

  • Usa scripts de MATLAB (ficheros .m). No uses Live Scripts (.mlx).

  • Crea un script ejercicioX.m individual por cada ejercicio.

  • Siempre ejecuta el script completo (botón “Run” o F5).

3.1 Aliasing

Tip✏️ Ejercicio 5

Crea una figura con dos subplots (2 x 1). En cada uno dibuja una senoide continua de amplitud unitaria en el intervalo [0, 1] s (1000 puntos) con plot y sus muestras a 5 Hz con stem:

  • Arriba: la senoide de 1 Hz
  • Abajo: la senoide de 6 Hz

1) ¿Qué ocurre al muestrear la senoide de 1 Hz? ¿Se produce aliasing? ¿Por qué?

2) ¿Qué ocurre al muestrear la senoide de 6 Hz? ¿Se produce aliasing? ¿Por qué?

3) Calcula la frecuencia digital, la digital equivalente y la frecuencia de la señal reconstruida a partir de las muestras para el caso de 6 Hz. Explica el resultado.

3.2 Cuantización

Tip✏️ Ejercicio 6

Implementa una función sqnr = calcula_sqnr(x, xq) que calcule la relación señal-ruido de cuantización (SQNR) en dB entre la señal original x y su versión cuantizada xq. Guarda esta función en un archivo llamado calcula_sqnr.m.

NOTA: el logaritmo en base 10 se calcula con la función log10() en MATLAB.

Para comprobarla, ejecuta calcula_sqnr([1 2 3], [1 2 4]) y deberías obtener 11.46 dB.

Tip✏️ Ejercicio 7

Genera dos periodos completos de una señal senoidal continua de 5 Hz y amplitud unitaria con 2000 puntos. Cuantízala con 2 bits en el rango [-1, 1] mediante redondeo usando la función cuant_redondeo.m proporcionada. Dibuja, en una figura con 2 x 1 paneles:

  • arriba: la señal original y la señal cuantizada superpuestas
  • abajo: el error de cuantización

Calcula la SQNR con tu función calcula_sqnr.m y muestra el valor en pantalla.

1) ¿Cuál es la media del error de cuantización? ¿Es positiva, negativa o cercana a cero? Si aplicáramos truncamiento, ¿qué forma tendría el error?

2) Ejecuta el script para distintos números de bits y observa cómo cambia la SQNR. ¿Cuántos dB aumenta la SQNR por cada bit que añades? ¿Cómo cambia el error de cuantización máximo con el número de bits? ¿Es coherente con la teoría?

SQNR: 12.22 dB

3.3 Reconstrucción

Tip✏️ Ejercicio 8
  • Genera 5 periodos completos de una senoidal “continua” de 10 Hz y amplitud unitaria con un vector de tiempos de 1000 puntos. Guarda la señal en xc y el vector de tiempos en tc.
  • Muestrea la misma señal a una frecuencia de muestreo de 50 Hz. Guarda la señal discreta en xn y los instantes de muestreo en tn.
  • Utiliza la función interp_ideal para reconstruir la señal discreta sobre el vector de tiempos tc y guarda el resultado en xc_ideal.
  • Dibuja la señal continua, la muestreada y la reconstruida en una misma gráfica, usando los mismos colores que en la figura del ZOH del trabajo previo: azul para la original, verde para la reconstruida y rojo para las muestras.

1) Disminuye el valor de la frecuencia de muestreo. ¿Funciona siempre la reconstrucción ideal? ¿Dónde está el límite? ¿Qué sucede cuando se cruza dicho límite? ¿Qué teorema lo explica?

2) Cerca de los extremos la reconstrucción no es exacta ni siquiera con \(f_s\) alta. ¿Por qué? (mira la fórmula del reconstructor ideal)

3) En la teoría se dice que la reconstrucción ideal no es causal. ¿Qué significa esto? Explica por qué la reconstrucción ideal no es causal, por qué el ZOH sí lo es y qué implicaciones tiene esto en la práctica.