3.1 Generación de una señal senoidal (DDS)

En este apartado se describe la técnica de generación de señales basada en la síntesis digital directa (DDS). El artículo siguiente describe su funcionamiento:

Algunas aplicaciones incluyen:

  • Generadores de señal programables: Para crear formas de onda precisas en instrumentación y equipos de prueba.

  • Síntesis de frecuencia: En transceptores de radio y sistemas de comunicaciones para generar portadoras y tonos de referencia.

  • Modulación digital: Para implementar técnicas de modulación FSK, PSK y QAM en sistemas de comunicación.

  • Sistemas de audio: Generación de tonos, efectos sonoros y síntesis musical en dispositivos embebidos.

  • Radar y sonar: Para generar chirps (barridos de frecuencia) y pulsos con características temporales específicas.

  • Control de motores: En drives de motores síncronos para generar referencias de velocidad y posición con alta resolución.

3.1.1 DDS:

Direct Digital Synthesis (DDS) es una técnica que a partir de un reloj de precisión y frecuencia fija, y utilizando bloques digitales, permite obtener una señal sinusoidal ajustable en fase y frecuencia.

En su forma más simple un DDS puede ser implementado como muestra la Figura 3-1, utilizando una señal de reloj, un contador, una tabla, y un conversor digital analógico D/A.

fig1

Figura 3-1. Algoritmo DDS.

La tabla SINE LOOKUP, almacena las amplitudes correspondientes a un ciclo completo de una senoide. La salida del contador, ADDRESS COUNTER, se utiliza como dirección de acceso a dicha tabla. A medida que el contador incrementa su valor, se obtienen secuencialmente las amplitudes que conforman la forma de onda generada. Finalmente, esta señal digital es transformada en una señal analógica mediante el conversor digital-analógico (D/A).

La frecuencia de salida, \(f_{o}\), depende de la frecuencia de la señal de reloj (frecuencia de muestreo), \(f_c\), y del número de muestras, \(2^N\), en que se ha codificado un periodo de la senoide (tamaño de la tabla):

\[f_o = \frac{f_c}{2^N}\]

Esta implementación no permite realizar ajustes en la frecuencia de salida de forma sencilla, para mejorarla se introduce un acumulador de fase, que permite ajustar la frecuencia de la señal de salida (fig. 3-2):

fig2

Figura 3-2. DDS con acumulador de fase.

En esta implementación se ha sustituido el contador de direcciones, por un contador de N bits con incremento (\(M\)) variable (acumulador de fase).

El acumulador de fase es un contador módulo \(2^N\), su funcionamiento es equivalente al de un vector rotando en un círculo de fase (ver figura 3-3). Cada punto en el círculo de fase corresponde con una muestra:

fig3

Figura 3-3. Acumulador de fase.

La frecuencia de la señal de salida viene dada por la expresión:

\[f_o = \frac{M \times f_c}{2^N}\]

donde:

  • \(M\): incremento de fase

  • \(N\): número de bits del acumulador de fase

  • \(f_c\): frecuencia de reloj (frecuencia de muestreo)

3.1.2 Implementación:

Utilizaremos la clase DDS16Bits para modelar un DDS con acumulador de fase de 16 bits.

fig3

Figura 3-4. Clase DDS16Bits.

  • Atributos:

    • phaseAccumulator: Valor del acumulador de fase

    • phaseIncrement: Valor del incremento de fase

    • SinLookupTbl (estático): Tabla con 257 valores de amplitud del primer cuadrante de un seno, codificados en 16 bits.

  • Métodos:

    • setPhase(): Asigna valor al acumulador de fase

    • setPhaseInc(): Asigna valor al incremento de fase

    • getNextSample(): Devuelve el valor de amplitud de un tono generado por DDS

    • SineTbl() (privado): Conversor fase → amplitud

Acumulador e incremento de fase:

El acumulador de fase y el incremento de fase se codifican en 16 bits (uint16_t).

El acumulador es un contador que se incrementa en cada ciclo de reloj con el valor del incremento de fase.

Un incremento de fase mayor dará lugar en una frecuencia de salida más alta, mientras que un incremento de fase menor a una frecuencia de salida más baja.

Veamos cómo obtener el valor del incremento de fase con un ejemplo:

\[f_c = 48\text{kHz}, \quad f_O = 1633\text{Hz}, \quad N = 16 \\ \quad \\ M = \frac{f_O \times 2^N}{f_c} = \frac{1633 \times 65536}{48000} = 2229.589\]

Si se codifica como uint16_t: \(M = \text{round}(2229.589) = 2230\)

Conversión fase → amplitud:

Utilizaremos una tabla de 1024 posiciones, pero para ahorrar recursos, sólo se guardarán en memoria los 257 valores de amplitud del primer cuadrante de la senoide, el resto se obtendrán aprovechando la simetría de la forma de onda.

Para crear la tabla y guardarla en un fichero de texto, podemos utilizar el siguiente script:

fases=0:pi/512:pi/2;
sine_tbl_257=round((2^15-1)*sin(fases));
dlmwrite("dds_sine_tbl257.dat",sine_tbl_257);
import numpy as np
fases = np.arange(0, np.pi/2 + np.pi/512, np.pi/512)
sine_tbl_257 = np.round((2**15 - 1) * np.sin(fases)).astype(int)
np.savetxt("dds_sine_tbl257.dat", sine_tbl_257, fmt='%d,')

El método SineTbl(uint16_t fase) obtiene los valores de amplitud en los 4 cuadrantes a partir del valor de fase.

La fase se codifica en en 16 bits, como la tabla es de 1024 posiciones (10 bits), se tomarán los 10 bits mas significativos de fase para acceder a la tabla.

fase10 = fase >> 6
- 1er cuadrante:     0 ≤ fase ≤ 16384: amplitud =  SinLookupTbl [fase10]‡.
- 2º cuadrante:  16384 < fase ≤ 32768: amplitud =  SinLookupTbl [512-fase10].
- 3er cuadrante: 32768 < fase ≤ 49152: amplitud = -SinLookupTbl [fase10-512].
- 4º cuadrante:  49152 < fase < 65536: amplitud = -SinLookupTbl [1024–fase10].

‡ SinLookupTbl [] → tabla con los valores de amplitud del primer cuadrante.
Diseño y pruebas clase DDS16Bits
  1. Preparación del entorno.

    • Descarga y descomprime el archivo de recursos lab3 en tu espacio de trabajo.

    • Los archivos dds.h y dds.c que implementan la clase DDS16Bits están ubicados en la carpeta src.

    • Crea el archivo dds_sine_tbl257.dat en la carpeta src utilizando el script de Matlab proporcionado.

  2. Configuración del proyecto.

    • Abre el proyecto lab3.

    • Selecciona el target Test_DDS, que utiliza un simulador para la depuración del código. Esto permite desarrollar y validar el software sin necesidad de disponer del hardware físico.

  3. Tareas a realizar.

    • Escribe el código de la clase DDS16Bits en el archivo dds.c.

    • Asegúrate de que el código compila correctamente y no genera errores.

    • Revisa los casos de prueba definidos en la función main(), ubicada en el archivo test/test_dds.c.

  4. Herramientas de análisis.

    • Inicializa el debugger ico_debug y abre la herramienta Analyzer desde el menú:

      View → Analysis Windows → Logic Analyzer.

      Analyzer mostrará la evolución temporal de las variables globales:

      • g_sample: número de muestra.

      • g_amp: amplitud de la señal.

    • Ejecuta la aplicación ico_run y corrige cualquier error funcional hasta que la clase DDS16Bits cumpla con la especificación deseada.

  5. Test y Resultados esperados.

    El fichero test/test_dds.c incluye los siguientes casos de prueba:

    • Test1 — Señal 1 kHz durante 10 ms.

    • Test2 — Señal 2 kHz durante 5 ms.

    • Test3 — Cambios de fase 0°-180°-0°-180°-0° con fo = 1 kHz.

    • Test4 — Oscilador parado (fo = 0 Hz) durante 5 ms.

    • Test5 — Chirp lineal (phase_inc incremental) durante 20 ms.

    Asegúrate de que todos los casos se ejecutan correctamente y producen los resultados esperados. Verifica que el periodo de la señal de 1 kHz corresponda con 48 muestras, y el de 2 kHz con 24 muestras.

    fig29