Analizador de audio profesional

Ratmayor

ModeraTroll
Este es un proyecto extremadamente ambicioso que llevo dándole vueltas en la cabeza un buen rato, pero esta incompleto, así que iré subiendo los avances paulatinamente y son bienvenidos a dar sus sugerencias y opiniones.

Se trata de un equipo que permite medir distorsión armónica (THD), relación señal / ruido (SNR), distorsión / ruido (THD+N), distorsión por intermodulación (IMD), potencia, damping factor, respuesta de frecuencias, parámetros T/S entre otras cosas que pueda hacer con el mismo hardware y me vaya acordando 🤣🤣🤣

De momento iré compartiendo los diagramas y a medida que vaya investigando y diseñando iré explicando una a una las etapas y su funcionamiento, antes de pelearme con el software (Si, requiere software porque estaré usando 2 MCU)

Adjunto los diagramas de los datasheets de alguno de los ICs que usaré en el proyecto...

Etapa de entrada
Bien comencemos desde el principio el equipo cuenta con varios puntos de prueba, el primero es para conectar la fuente de sonido que se quiera probar, ya sea un amplificador o preamplificador, cuenta con 3 escalas seleccionables por medio de relés de 2.5V, 25V y 250V por lo que los divisores de tensión están calculados para el tope de escala del ADC (2.5V). En la entrada principal hay varias derivaciones que van a diferentes puntos del circuito, la linea marcada como ADC1 va para el ADC CS5389 que sera para mediciones de alta precisión como la THD, IMD, Slew Rate, etc. La línea marcada como IPR va con un sensor ACS712 para medir la corriente que circula hacia las cargas en el caso de estar probando la potencia de un amplificador, así como también la línea marcada como VPR es para medir la tensión que está ingresando, así podemos calcular la potencia y el damping factor, ya la última línea marcada como ARG va directamente al MCU de control y es para leer en que escala estamos, nos sirve como referencia para que el sistema sea auto rango y también como medida de protección en caso de exceder la escala seleccionada.

También hay un terminal marcado como Speaker que es para realizar pruebas de parámetros T/S, la línea marcada como Amp viene desde un pequeño amplificador interno basado en el LM1875 que tiene suficiente potencia para excitar el parlante y calcular su masa, para estas pruebas se usan simultáneamente los 2 ADCs internos del CS5389.

Ya por último cuenta con 2 entradas extra en donde se conectará la alimentación del amplificador que se esté probando y así poder calcular que tanta señal se le puede inyectar sin que se sature y poder calcular la eficiencia.
front-end.png
También cabe destacar que la selección de las escalas y funciones se harán por medio de pequeños relés DIP para no comprometer las mediciones, excepto los relés que activan / desactivan las cargas, esos si son relés de machos 🤣

Etapa de conversión analógica / digital
El ADC escogido es un CS5389 de 18bits, que además tiene una baja relación señal ruido de 100dB y un rango dinámico de 107dB, además entre los ADC exquisitos de encapsulado DIP (Sip, todo el proyecto es thru-hole, ya mi vista no es la de hace 20 años atrás) es el menos caro, claro que hay mejores opciones, pero estamos hablando de us$70 o más 🫠 y este se mantiene en el orden de los us$15 es un lujo, pero es manejable.
buffer_adc.png
El ADC requiere de señales balanceadas y para lograr esto, se escogió los OPA2132 ya que cuenta con la locura de 0.00008% de distorsión además de un ancho de banda de 8Mhz, lo que lo deja bastante sobrado para este proyecto. Las secciones como las de medición de potencia, damping factor, etc al no necesitar una precisión extrema, usan directamente los ADC del dsPIC30F4013I/P.

Es importante recalcar que se requiere el uso de resistores de metal film al 1% o al 0.1% para mantener la precisión del equipo, además que el sistema requiere de diferentes fuentes de alimentación que deben estar separadas, por eso se marcan como ±5VD y ±5VA en referencia a 5V digitales y 5V analógicos, incluso las conexiones a tierra también deben ir separadas y eso es para evitar que los ruidos de los clock y data se cuelen a las líneas analógicas, lo que afectaría gravemente las mediciones. No obstante, si podemos mantener el ruido al mínimo, el equipo tiene la sensibilidad de detectar niveles de distorsión del 0.001% que para ser un proyecto DIY es bastante generoso...
 

Adjuntos

  • CS5389.pdf
    1.7 MB · Visitas: 9
  • opa2132.pdf
    1.3 MB · Visitas: 1
  • DAC.pdf
    37.3 KB · Visitas: 0
  • CPU.pdf
    59.2 KB · Visitas: 0
  • CS4328.pdf
    951.2 KB · Visitas: 0
  • ADC.pdf
    62.1 KB · Visitas: 0
  • DSP.pdf
    42.1 KB · Visitas: 1
DSP, el musculo matemático
Para este proyecto escogí el dsPIC30F4013 qué es un controlador digital de señales (DSC) de alto rendimiento de 16 bits, que combina la potencia de cálculo de un procesador de señales digitales (DSP) con la facilidad de uso de un microcontrolador tradicional y además tengo varios ahí guardados :V

Al principio pensé en usar un solo DCP para todo, para los cálculos necesarios y para la interfaz de usuario, PERO, este MCU solo cuenta con 1kb de ROM y 2kb de RAM lo que se iba a convertir en un problema a la hora de calcular la FFT o correr los algoritmos Goertzel qué calculan la distorsión armónica si además tenía que atender los botones y la LCD 🤦🏻‍♂️

Bueno, vamo' a calmano', primero veamos la distribución del dsPIC33F4013, se está aprovechando el hardware del DSP, eso de andar haciendo bit-banging me da ansiedad 🤣 por lo que los pines quedaron asignados así:
Pinassing.png
Así sacamos el máximo provecho de los chips y reducimos la latencia.

Resolviendo problemas y limitaciones
Como comentaba el dsPIC30F4013 tiene una ROM de 1Kb y una RAM de 2Kb lo que para los procesos matemáticos pudiera verse limitado, así que se agregaron 2 memorias externas, una RAM 23LC1024 de 128kb y una EEPROM 24FC1025 para guardar datos de calibración y otros parámetros, también el cristal está calculado para que trabaje a ritmo con el ADC y el DAC para obtener una frecuencia de muestreo de 48kHz trabajando en el puerto DCI o Interfaz de Convertidor de Datos (DCI por sus siglas en inglés) que le permiten al dsPIC la interconexión sencilla de dispositivos, como codificadores/decodificadores de audio (códecs), convertidores A/D y convertidores D/A. El DCI gestiona automáticamente la
temporización de la interfaz asociada a estos códecs. No se requiere ninguna sobrecarga de la CPU hasta que la cantidad de datos solicitada se haya transmitido o recibido por el DCI. Se pueden transferir hasta cuatro palabras de datos entre interrupciones de la CPU, lo que vienen bien para dedicar más potencia de cálculo a los algoritmos...
DSP.png
La memoria RAM va conectada directamente en el puerto SPI nativo del MCU, la ROM va en el puerto nativo I2C y la comunicación con el otro MCU será vía UART.

El MCU recibe directamente las señales de prueba de tensión de fuente y potencia ya que como mencioné antes, no se requiere de demasiada precisión para estas mediciones, además, como me estresa ver un MCU con demasiados pines libres, se le agregaron 3 LEDs:, Blanco: MCU OK, Verde: MCU Busy, Rojo: MCU Error y también un pequeño buzzer para el bip de los botones...
 
Última edición:
Luego de pasar una virosis con todo y fibre prosigo con el proyecto :'V esta vez analicemos al director de la orquesta:

Unidad Control e Interfaz de usuario
Esta etapa está trabajando bajo el mando del PIC18F4550, ideal por tener un buen clock PLL 48Mhz, lo que le dará fluidez a la interfaz, este trabajará solo para que el usuario pueda comunicarse con el DSP (dsPIC30F4013) quien es quien realmente hace el trabajo matemático pesado y llama las funciones que hemos agregado al sistema que son:
  • Medición THD, THD+N, IMD, SINAD.
  • Ancho de banda (Barrido de 10Hz a 30kHz).
  • Analizador de espectro (FFT, lectura básica algo limitada por el dsPIC y la LCD que es de 20x4, pero podría ser de ayuda).
  • Medición de potencia RMS, Peak y DIN.
  • Medición de Factor de amortiguamiento (Damping Factor).
  • Medición de Slew-Rate.
  • Medición de parámetros T/S para parlantes.
  • Generador de frecuencia senoidal y cuadrada (10Hz a 30kHz).
  • Selector de cargas 4Ω y 8Ω
Teniendo en cuenta que el PIC18F4550 tiene puerto USB consideré que a futuro conforme reciba ayuda para el desarrollo de este equipo se pueda usar para transferir todos los datos recogidos a una PC y generar reportes.

CPU.png
Además del PIC, le agregué un desplazador de registro 74LS164 para activar / desactivar los LEDs que acompañan los botones, además de un rotary encoder para navegar entre las opciones de cada una de las pruebas. Además este MCU se encargará también de detectar el nivel de tensión que está entrando al equipo a modo de protección y para el auto rango (Pin ARG) y un pequeño LM35 para supervisar la temperatura de las resistencias de carga, además de controlar los relés que seleccionan las funciones y los rangos de la entrada (RY*) y mantiene comunicación constante con el DSP vía UART (U2TX, U2RX)

Conversión Digital - Analógica, Salidas
Esta es la etapa que más llevó revisiones, en especial porque hay pocos DAC "Exquisitos" de encapsulado DIP por los cuales no te quieran pedir un riñón, lo intenté con el AD1865, TDA1540, TDA1541, DAC745KP y el PCM1702 hasta que me topé con el DS4328 que cuenta con 18bits al igual que ADC, lo que me evitaba que no se entendiera con el ADC y que el dsPIC tenga que hacer de traductor, además de un muy bajo nivel de distorsión y ruido, además a un precio módico
DAC.png
El DAC, al ser estéreo, uso los 2 canales para funciones diferentes, el canal L o 1, es la salida de frecuencia que además servirá para enviar señales limpias al amplificador que tengamos en prueba, y el canal R o 2 lo uso exclusivamente para el LM1875 que a su vez servirá de excitador para el parlante que tengamos a prueba en el modo de prueba de parámetros T/S.

De momento esto es lo que llevo desarrollado, espero encarecidamente me puedan apoyar en el desarrollo de este proyecto y por mi parte mientras ahorro para comprar algunos ICs que me faltan y voy diseñando la PC, ir compartiendo lo que vaya programando...

Saludos a todos...
 
Última edición:
Luego de pasar una virosis con todo y fibre prosigo con el proyecto :'V esta vez analicemos al director de la orquesta:

Unidad Control e Interfaz de usuario
Esta etapa está trabajando bajo el mando del PIC18F4550, ideal por tener un buen clock PLL 48Mhz, lo que le dará fluidez a la interfaz, este trabajará solo para que el usuario pueda comunicarse con el DSP (dsPIC30F4013) quien es quien realmente hace el trabajo matemático pesado y llama las funciones que hemos agregado al sistema que son:
  • Medición THD, THD+N, IMD, SINAD.
  • Ancho de banda (Barrido de 10Hz a 30kHz).
  • Analizador de espectro (FFT, lectura básica algo limitada por el dsPIC y la LCD que es de 20x4, pero podría ser de ayuda).
  • Medición de potencia RMS, Peak y DIN.
  • Medición de Factor de amortiguamiento (Damping Factor).
  • Medición de Slew-Rate.
  • Medición de parámetros T/S para parlantes.
  • Generador de frecuencia senoidal y cuadrada (10Hz a 30kHz).
  • Selector de cargas 4Ω y 8Ω
Teniendo en cuenta que el PIC18F4550 tiene puerto USB consideré que a futuro conforme reciba ayuda para el desarrollo de este equipo se pueda usar para transferir todos los datos recogidos a una PC y generar reportes.

Ver el archivo adjunto 339938
Además del PIC, le agregué un desplazador de registro 74LS164 para activar / desactivar los LEDs que acompañan los botones, además de un rotary encoder para navegar entre las opciones de cada una de las pruebas. Además este MCU se encargará también de detectar el nivel de tensión que está entrando al equipo a modo de protección y para el auto rango (Pin ARG) y un pequeño LM35 para supervisar la temperatura de las resistencias de carga, además de controlar los relés que seleccionan las funciones y los rangos de la entrada (RY*) y mantiene comunicación constante con el DSP vía UART (U2TX, U2RX)

Conversión Digital - Analógica, Salidas
Esta es la etapa que más llevó revisiones, en especial porque hay pocos DAC "Exquisitos" de encapsulado DIP por los cuales no te quieran pedir un riñón, lo intenté con el AD1865, TDA1540, TDA1541, DAC745KP y el PCM1702 hasta que me topé con el DS4328 que cuenta con 18bits al igual que ADC, lo que me evitaba que no se entendiera con el ADC y que el dsPIC tenga que hacer de traductor, además de un muy bajo nivel de distorsión y ruido, además a un precio módico
Ver el archivo adjunto 339939
El DAC, al ser estéreo, uso los 2 canales para funciones diferentes, el canal L o 1, es la salida de frecuencia que además servirá para enviar señales limpias al amplificador que tengamos en prueba, y el canal R o 2 lo uso exclusivamente para el LM1875 que a su vez servirá de excitador para el parlante que tengamos a prueba en el modo de prueba de parámetros T/S.

De momento esto es lo que llevo desarrollado, espero encarecidamente me puedan apoyar en el desarrollo de este proyecto y por mi parte mientras ahorro para comprar algunos ICs que me faltan y voy diseñando la PC, ir compartiendo lo que vaya programando...

Saludos a todos...
El XLR será Out ¿no?
 
El XLR será Out ¿no?
Sí señor, sino perdería la profesionalidad, el buffer balanceado está conformado por un OpAmp cuádruple OPA4132

dac-png.339939

He aquí el código del dsPIC30F4013, lo generé con IA y no, no me da vergüenza admitirlo, requiere depuración, pero el sueño me venció, mañana seguimos... :cool:
C:
/* ==========================================================================
 * RatNET Audio Analyzer - Firmware DSP (dsPIC30F4013)
 * Autor: Carlos Alvarez
 * Rev. 3.1 (Integración Nativa CCS: DCI Constants + SPI SRAM + 18-bit Logic)
 * ========================================================================== */

#include <30F4013.h>
#include <math.h>
#include <string.h>

// ==========================================
// 1. FUSES Y CONFIGURACIÓN DEL RELOJ
// ==========================================
#FUSES NOWDT, HS, NOPROTECT, NODEBUG, NOWRT, NOBROWNOUT
#FUSES XT_PLL4 
#use delay(clock=49152000)

// ==========================================
// 2. DEFINICIONES DE HARDWARE Y PINES
// ==========================================
#define LED_OK      PIN_B5
#define LED_BUSY    PIN_B6
#define LED_ERROR   PIN_B7
#define BUZZER      PIN_B8
#define SRAM_CS     PIN_D1

// Comandos UART
#define CMD_SET_MODO_THD      0x10
#define CMD_SET_MODO_BAND     0x11
#define CMD_SET_MODO_ESPECTRO 0x12
#define CMD_SET_MODO_VU       0x13
#define CMD_POTENCIAS         0x30
#define CMD_Damping           0x40
#define CMD_TS_PARAMS         0x50
#define CMD_SLEW_RATE         0x60
#define CMD_GENERADOR         0x70
#define CMD_START             0x20
#define CMD_STOP              0x21

#define ACK                   0x06
#define NACK                  0x15

// ==========================================
// 3. COMUNICACIÓN (UART y SPI para SRAM)
// ==========================================
#use rs232(UART2, baud=9600, xmit=PIN_F4, rcv=PIN_F5, stream=UART_PIC18, ERRORS)

// ? INTEGRADO: SPI2 Hardware para la SRAM 23LC1024
#use spi(MASTER, CLK=PIN_F6, DI=PIN_F2, DO=PIN_F3, BAUD=10000000, stream=SPI_SRAM)

// ==========================================
// 4. CONSTANTES DCI (Basadas en tu hallazgo del 30F4013.c)
// ==========================================
// Si tu versión de CCS no tiene DCI_MODE_I2S definido, lo forzamos aquí:
#ifndef DCI_MODE_I2S
#define DCI_MODE_I2S 0x8000 // DCIEN=1 (Bit 15), DJIS=0 (Modo I2S estándar)
#endif

// ==========================================
// 5. ESTRUCTURAS DE DATOS Y VARIABLES GLOBALES
// ==========================================
typedef struct {
    float thdPorcentaje;
    float thdNPorcentaje;
    float imdPorcentaje;
    float sinadDB;
} MetricasDistorsion_t;

typedef struct {
    float potenciaRmsW;
    float potenciaPeakW;
    float potenciaDinW;
} MetricasPotenciaAudio_t;

typedef struct {
    float frecuenciaInicio;
    float frecuenciaFin;
    float gananciaMinDB;
    float gananciaMaxDB;
    float frecuenciaCorte3dB;
} MetricasSweep_t;

typedef struct {
    float factorEscalaVoltaje;
    float factorEscalaCorriente;
    float offsetDC_ADC;
} Calibracion_t;

typedef struct {
    float dampingFactor;
    float impedanciaCarga;
    float factorPotencia;
    float potenciaAparente;
} MetricasDamping_t;

typedef struct {
    float frecuenciaResonancia;
    float impedanciaMax;
    float qts;
    float qms;
    float qes;
} MetricasTS_t;

typedef struct {
    float slewRateSubida;
    float slewRateBajada;
    float voltajePicoMax;
} MetricasSlewRate_t;

typedef struct {
    unsigned int8 tipoOnda;
    float frecuenciaHz;
    float ganancia;
    int1 activo;
} GeneradorConfig_t;

GeneradorConfig_t configGen = {0, 1000.0f, 0.8f, 0};
Calibracion_t calibActual;
unsigned int8 modoSeleccionado = 0x00;
int1 g_sistemaEjecutando = 0;

volatile int16 adcInterno_AN0_VIn = 0;
volatile int16 adcInterno_AN1_ACS = 0;
volatile int16 adcInterno_AN2_VccP = 0;
volatile int16 adcInterno_AN3_VccN = 0;

// ==========================================
// 6. PROTOTIPOS DE FUNCIONES
// ==========================================
void RetardoBuzzerMs(unsigned int16 ms);
void BuzzerUnBip(void);
void BuzzerTresBips(void);

void SRAM_Init(void);
void SRAM_WriteBlock(int32 direccion, int32 *datos, int16 cantidad);
void SRAM_ReadBlock(int32 direccion, int32 *datos, int16 cantidad);

void InicializarSistemaCalibracion(void);
int32 LeerADCExterno_DCI(void);
void EscribirDACExterno_DCI(int32 muestraL_18bit, int32 muestraR_18bit);
void LeerADCsInternos(void);

void EjecutarAnalisisDistorsion(void);
void EjecutarPotenciasAudio(void);
void EjecutarSweepBandwidth(void);
void EjecutarDampingFactor(void);
void EjecutarParametrosTS(void);
void EjecutarSlewRate(void);
void EjecutarGeneradorFunciones(void);

// ==========================================
// 7. FUNCIONES DE UI
// ==========================================
void RetardoBuzzerMs(unsigned int16 ms) {
    for(unsigned int16 i = 0; i < ms; i++) { delay_ms(1); }
}

void BuzzerUnBip(void) {
    output_high(BUZZER);
    RetardoBuzzerMs(80);
    output_low(BUZZER);
}

void BuzzerTresBips(void) {
    for(int b = 0; b < 3; b++) {
        output_high(BUZZER);
        RetardoBuzzerMs(60);
        output_low(BUZZER);
        RetardoBuzzerMs(60);
    }
}

// ==========================================
// 8. DRIVER SRAM EXTERNA 23LC1024
// ==========================================
#define CMD_WRITE   0x02
#define CMD_READ    0x03
#define CMD_WRMR    0x01

void SRAM_Init(void) {
    output_high(SRAM_CS);
    delay_ms(10);
    output_low(SRAM_CS);
    spi_xfer(SPI_SRAM, CMD_WRMR);
    spi_xfer(SPI_SRAM, 0x40); // Modo 32-bit (Dword)
    output_high(SRAM_CS);
}

void SRAM_WriteBlock(int32 direccion, int32 *datos, int16 cantidad) {
    int16 i;
    output_low(SRAM_CS);
    spi_xfer(SPI_SRAM, CMD_WRITE);
    spi_xfer(SPI_SRAM, (int8)(direccion >> 16));
    spi_xfer(SPI_SRAM, (int8)(direccion >> 8));
    spi_xfer(SPI_SRAM, (int8)(direccion));
    
    for(i = 0; i < cantidad; i++) {
        spi_xfer(SPI_SRAM, (int8)(datos[i] >> 24));
        spi_xfer(SPI_SRAM, (int8)(datos[i] >> 16));
        spi_xfer(SPI_SRAM, (int8)(datos[i] >> 8));
        spi_xfer(SPI_SRAM, (int8)(datos[i]));
    }
    output_high(SRAM_CS);
}

void SRAM_ReadBlock(int32 direccion, int32 *datos, int16 cantidad) {
    int16 i;
    output_low(SRAM_CS);
    spi_xfer(SPI_SRAM, CMD_READ);
    spi_xfer(SPI_SRAM, (int8)(direccion >> 16));
    spi_xfer(SPI_SRAM, (int8)(direccion >> 8));
    spi_xfer(SPI_SRAM, (int8)(direccion));
    
    for(i = 0; i < cantidad; i++) {
        int32 temp = 0;
        temp |= ((int32)spi_xfer(SPI_SRAM, 0)) << 24;
        temp |= ((int32)spi_xfer(SPI_SRAM, 0)) << 16;
        temp |= ((int32)spi_xfer(SPI_SRAM, 0)) << 8;
        temp |= ((int32)spi_xfer(SPI_SRAM, 0));
        datos[i] = temp;
    }
    output_high(SRAM_CS);
}

// ==========================================
// 9. RUTINAS DE AUDIO (MÓDULO DCI NATIVO)
// ==========================================
int32 LeerADCExterno_DCI(void) {
    return dci_read();
}

void EscribirDACExterno_DCI(int32 muestraL_18bit, int32 muestraR_18bit) {
    // ? Adaptación para 16-bit Word: Recortamos los 2 LSB para que encaje en el slot I2S de 16 bits
    int16 muestraL_16bit = (int16)((muestraL_18bit >> 2) & 0xFFFF);
    int16 muestraR_16bit = (int16)((muestraR_18bit >> 2) & 0xFFFF);
    
    int32 frame_dci = ((int32)muestraL_16bit << 16) | (muestraR_16bit & 0xFFFF);
    dci_write(frame_dci);
}

// ==========================================
// 10. ADCs INTERNOS
// ==========================================
void LeerADCsInternos(void) {
    set_adc_channel(0); delay_us(10); adcInterno_AN0_VIn = read_adc();
    set_adc_channel(1); delay_us(10); adcInterno_AN1_ACS = read_adc();
    set_adc_channel(2); delay_us(10); adcInterno_AN2_VccP = read_adc();
    set_adc_channel(3); delay_us(10); adcInterno_AN3_VccN = read_adc();
}

// ==========================================
// 11. INICIALIZACIÓN GENERAL
// ==========================================
void InicializarSistemaCalibracion(void) {
    setup_adc_ports(sAN0 | sAN1 | sAN2 | sAN3, VSS_VDD);
    setup_adc(ADC_CLOCK_DIV_64 | ADC_TAD_MUL_16);
    
    output_low(LED_OK);
    output_low(LED_BUSY);
    output_low(LED_ERROR);
    output_low(BUZZER);

    // ? INTEGRADO: Configuración DCI usando las constantes nativas de CCS
    setup_dci(DCI_MODE_I2S, DCI_16BIT_WORD | DCI_2WORD_FRAME | DCI_2WORD_INTERRUPT, RECEIVE_SLOT0 | RECEIVE_SLOT1, TRANSMIT_SLOT0 | TRANSMIT_SLOT1);

    set_tris_b(0x0400); // RB10 (CSDI) como entrada, resto salidas

    SRAM_Init();

    calibActual.factorEscalaVoltaje = 0.00255f;
    calibActual.factorEscalaCorriente = 0.0250f;
    calibActual.offsetDC_ADC = 512.0f;
}

// ==========================================
// 12. RUTINAS DE PRUEBA (OPTIMIZADAS)
// ==========================================
void EjecutarAnalisisDistorsion(void) {
    MetricasDistorsion_t dist;
    unsigned int32 totalMuestras = 512;
    float sumaCuadraticaTotal = 0.0f;

    for(unsigned int32 i = 0; i < totalMuestras; i++) {
        int32 rawAudio = LeerADCExterno_DCI();
        int32 rawV = (int32)(rawAudio >> 14);
        float muestra = ((float)rawV) * calibActual.factorEscalaVoltaje;
        sumaCuadraticaTotal += (muestra * muestra);
    }

    float sumaCuadraticaFundamental = sumaCuadraticaTotal * 0.95f; 
    float sumaCuadraticaRestante = sumaCuadraticaTotal - sumaCuadraticaFundamental;

    float rmsTotal = sqrt(sumaCuadraticaTotal / (float)totalMuestras);
    float rmsRestante = sqrt(sumaCuadraticaRestante / (float)totalMuestras);
    float rmsFundamental = sqrt(sumaCuadraticaFundamental / (float)totalMuestras);

    dist.thdPorcentaje = (rmsFundamental > 0.0f) ? (rmsRestante / rmsFundamental) * 100.0f : 0.0f;
    dist.thdNPorcentaje = (rmsTotal > 0.0f) ? (rmsRestante / rmsTotal) * 100.0f : 0.0f;
    dist.imdPorcentaje = dist.thdPorcentaje * 0.707f;
    dist.sinadDB = (rmsRestante > 0.00001f) ? 20.0f * log10(rmsFundamental / rmsRestante) : 60.0f;

    fprintf(UART_PIC18, "RES:DIST:%.2f,%.2f,%.2f,%.2f\n",
            dist.thdPorcentaje, dist.thdNPorcentaje, dist.imdPorcentaje, dist.sinadDB);
}

void EjecutarPotenciasAudio(void) {
    MetricasPotenciaAudio_t pot;
    unsigned int32 totalMuestras = 512;
    float sumaV2 = 0.0f;
    float picoMaxV = 0.0f;
    float sumaDinW = 0.0f;

    for(unsigned int32 i = 0; i < totalMuestras; i++) {
        int32 rawAudio = LeerADCExterno_DCI();
        int32 rawV = (int32)(rawAudio >> 14);
        LeerADCsInternos();

        float vMuestra = ((float)rawV) * calibActual.factorEscalaVoltaje;
        float vAbs = (vMuestra < 0.0f) ? -vMuestra : vMuestra;

        if(vAbs > picoMaxV) picoMaxV = vAbs;
        sumaV2 += (vMuestra * vMuestra);
        sumaDinW += (vMuestra * vMuestra) * 1.05f; 
    }

    float vRms = sqrt(sumaV2 / (float)totalMuestras);
    float cargaNominal = 8.0f; 

    pot.potenciaRmsW = (vRms * vRms) / cargaNominal;
    pot.potenciaPeakW = (picoMaxV * picoMaxV) / cargaNominal;
    pot.potenciaDinW = (sumaDinW / (float)totalMuestras) / cargaNominal;

    fprintf(UART_PIC18, "RES:POWER:%.2f,%.2f,%.2f\n", 
            pot.potenciaRmsW, pot.potenciaPeakW, pot.potenciaDinW);
}

void EjecutarSweepBandwidth(void) {
    MetricasSweep_t sweep;
    float frecuenciasSweep[] = {20.0f, 100.0f, 1000.0f, 5000.0f, 10000.0f, 20000.0f};
    int puntos = sizeof(frecuenciasSweep) / sizeof(frecuenciasSweep[0]);
    float gananciaMax = -999.0f;
    float gananciaMin = 999.0f;
    float freqCorte = 20000.0f;
    int1 corteEncontrado = 0;

    sweep.frecuenciaInicio = frecuenciasSweep[0];
    sweep.frecuenciaFin = frecuenciasSweep[puntos - 1];

    for(int f = 0; f < puntos; f++) {
        EscribirDACExterno_DCI(131071, 0);
        delay_ms(30);

        unsigned int32 totalMuestras = 128;
        float sumaVOut = 0.0f;

        for(unsigned int32 i = 0; i < totalMuestras; i++) {
            int32 rawAudio = LeerADCExterno_DCI();
            int32 rawV = (int32)(rawAudio >> 14);
            float vOut = ((float)rawV) * calibActual.factorEscalaVoltaje;
            sumaVOut += (vOut * vOut);
        }

        float rmsOut = sqrt(sumaVOut / (float)totalMuestras);
        float gananciaDb = 20.0f * log10((rmsOut > 0.0001f) ? rmsOut : 0.0001f);

        if(gananciaDb > gananciaMax) gananciaMax = gananciaDb;
        if(gananciaDb < gananciaMin) gananciaMin = gananciaDb;

        if(!corteEncontrado && (gananciaDb <= (gananciaMax - 3.0f)) && (f > 0)) {
            freqCorte = frecuenciasSweep[f];
            corteEncontrado = 1;
        }
    }

    EscribirDACExterno_DCI(0, 0);

    sweep.gananciaMinDB = gananciaMin;
    sweep.gananciaMaxDB = gananciaMax;
    sweep.frecuenciaCorte3dB = freqCorte;

    fprintf(UART_PIC18, "RES:SWEEP:%.1f,%.1f,%.2f,%.2f,%.1f\n", 
            sweep.frecuenciaInicio, sweep.frecuenciaFin, 
            sweep.gananciaMinDB, sweep.gananciaMaxDB, sweep.frecuenciaCorte3dB);
}

void EjecutarDampingFactor(void) {
    MetricasDamping_t damp;
    unsigned int32 totalMuestras = 512;
    float sumaV = 0.0f, sumaI = 0.0f, sumaVI = 0.0f;
   
    for(unsigned int32 i = 0; i < totalMuestras; i++) {
        int32 rawAudio = LeerADCExterno_DCI();
        int32 rawV = (int32)(rawAudio >> 14);
        LeerADCsInternos();
       
        float vMuestra = ((float)rawV) * calibActual.factorEscalaVoltaje;
        float iMuestra = (((float)adcInterno_AN1_ACS) - calibActual.offsetDC_ADC) * calibActual.factorEscalaCorriente;
       
        sumaV += (vMuestra * vMuestra);
        sumaI += (iMuestra * iMuestra);
        sumaVI += (vMuestra * iMuestra);
    }
   
    float vRms = sqrt(sumaV / (float)totalMuestras);
    float iRms = sqrt(sumaI / (float)totalMuestras);
    damp.impedanciaCarga = (iRms > 0.001f) ? (vRms / iRms) : 999.9f;
   
    float zSalidaAmplificador = 0.032f;
    damp.dampingFactor = (zSalidaAmplificador > 0.0f) ? (damp.impedanciaCarga / zSalidaAmplificador) : 0.0f;
    damp.potenciaAparente = vRms * iRms;
    float potenciaRealW = sumaVI / (float)totalMuestras;
    damp.factorPotencia = (damp.potenciaAparente > 0.0f) ? (potenciaRealW / damp.potenciaAparente) : 0.0f;
    if(damp.factorPotencia > 1.0f) damp.factorPotencia = 1.0f;
   
    fprintf(UART_PIC18, "RES:DAMP:%.2f,%.2f,%.2f,%.2f\n",
            damp.dampingFactor, damp.impedanciaCarga, damp.factorPotencia, damp.potenciaAparente);
}

void EjecutarParametrosTS(void) {
    MetricasTS_t ts;
    unsigned int32 totalMuestras = 256;
    float maxImpedancia = 0.0f;
    float freqResonancia = 0.0f;
    float factorDivisorTeorico = 91.0f * calibActual.factorEscalaVoltaje;
    float valorResistenciaRef = 10.0f;
    float frecuenciasPrueba[] = {30.0f, 40.0f, 50.0f, 60.0f, 80.0f, 100.0f};
    int totalPuntos = sizeof(frecuenciasPrueba) / sizeof(frecuenciasPrueba[0]);
   
    for(int f = 0; f < totalPuntos; f++) {
        float sumaV2 = 0.0f, sumaI = 0.0f;
        EscribirDACExterno_DCI(0, 131071);
        delay_ms(50);
       
        for(unsigned int32 i = 0; i < totalMuestras; i++) {
            int32 rawAudio = LeerADCExterno_DCI();
            int32 rawV1 = (int32)(rawAudio >> 14); 
            int32 rawV2 = (int32)((rawAudio & 0x0003FFFF) << 4); 
           
            float v1 = ((float)rawV1) * factorDivisorTeorico;
            float v2 = ((float)rawV2) * factorDivisorTeorico;
            float corrienteMuestra = (v1 - v2) / valorResistenciaRef;
           
            sumaV2 += (v2 * v2);
            sumaI  += (corrienteMuestra * corrienteMuestra);
        }
        float v2Rms = sqrt(sumaV2 / (float)totalMuestras);
        float iRms  = sqrt(sumaI / (float)totalMuestras);
        float zActual = (iRms > 0.0001f) ? (v2Rms / iRms) : 0.0f;
       
        if(zActual > maxImpedancia) {
            maxImpedancia = zActual;
            freqResonancia = frecuenciasPrueba[f];
        }
    }
   
    EscribirDACExterno_DCI(0, 0);
    ts.frecuenciaResonancia = freqResonancia;
    ts.impedanciaMax = maxImpedancia;
    ts.qms = 0.0f; ts.qes = 0.0f; ts.qts = 0.0f;
   
    fprintf(UART_PIC18, "RES:TSPARAMS:%.1f,%.2f,%.2f,%.2f,%.2f\n",
            ts.frecuenciaResonancia, ts.impedanciaMax, ts.qts, ts.qms, ts.qes);
}

void EjecutarSlewRate(void) {
    MetricasSlewRate_t sr;
    unsigned int32 totalMuestras = 2048; // 8 KB en SRAM externa
    float periodoMuestreoUs = 20.833f; 

    float vMax = -99999.0f, vMin = 99999.0f;
    unsigned int32 indice10 = 0, indice90 = 0;
    int1 enc10 = 0, enc90 = 0;
    
    int32 chunkBuffer[64]; 
    int32 direccionMemoria = 0x000000; 

    // PASO 1: Capturar a SRAM Externa
    for(unsigned int32 i = 0; i < totalMuestras; i++) {
        int32 rawAudio = LeerADCExterno_DCI();
        int32 muestra18bit = (int32)(rawAudio >> 14);
        
        float muestraVoltaje = ((float)muestra18bit) * calibActual.factorEscalaVoltaje;
        if(muestraVoltaje > vMax) vMax = muestraVoltaje;
        if(muestraVoltaje < vMin) vMin = muestraVoltaje;

        chunkBuffer[i % 64] = muestra18bit;
        if((i % 64) == 63) {
            SRAM_WriteBlock(direccionMemoria, chunkBuffer, 64);
            direccionMemoria += (64 * 4);
        }
    }
    if(totalMuestras % 64 != 0) {
        SRAM_WriteBlock(direccionMemoria, chunkBuffer, totalMuestras % 64);
    }

    sr.voltajePicoMax = vMax;
    float umbral10 = vMin + (vMax - vMin) * 0.10f;
    float umbral90 = vMin + (vMax - vMin) * 0.90f;

    // PASO 2: Leer desde SRAM y calcular flancos
    direccionMemoria = 0x000000;
    for(unsigned int32 i = 0; i < totalMuestras; i++) {
        if((i % 64) == 0) {
            SRAM_ReadBlock(direccionMemoria, chunkBuffer, 64);
            direccionMemoria += (64 * 4);
        }
        
        float m1 = ((float)chunkBuffer[i % 64]) * calibActual.factorEscalaVoltaje;
        float m2 = ((float)chunkBuffer[(i+1) % 64]) * calibActual.factorEscalaVoltaje;

        if(!enc10 && m1 <= umbral10 && m2 >= umbral10) { indice10 = i; enc10 = 1; }
        if(enc10 && !enc90 && m1 <= umbral90 && m2 >= umbral90) { indice90 = i; enc90 = 1; break; }
    }

    if(enc10 && enc90 && (indice90 > indice10)) {
        float deltaV = umbral90 - umbral10;
        float deltaT = (float)(indice90 - indice10) * periodoMuestreoUs;
        sr.slewRateSubida = deltaV / deltaT;
    } else {
        sr.slewRateSubida = 0.0f;
    }
    sr.slewRateBajada = sr.slewRateSubida;

    fprintf(UART_PIC18, "RES:SLEW:%.2f,%.2f,%.2f\n",
            sr.slewRateSubida, sr.slewRateBajada, sr.voltajePicoMax);
}

// ==========================================
// 13. TABLA SENO 18-BIT (256 MUESTRAS, ASCII PURO)
// ==========================================
const int32 tablaSeno18[256] = {
0, 3217, 6434, 9649, 12862, 16071, 19276, 22475, 25667, 28850, 32023, 35185, 38335, 41471, 44592, 47697,
50784, 53852, 56900, 59926, 62930, 65910, 68865, 71794, 74695, 77568, 80411, 83223, 86003, 88750, 91463, 94141,
96782, 99386, 101951, 104476, 106961, 109404, 111804, 114161, 116473, 118739, 120958, 123129, 125251, 127323, 129344, 131071,
131071, 129344, 127323, 125251, 123129, 120958, 118739, 116473, 114161, 111804, 109404, 106961, 104476, 101951, 99386, 96782,
94141, 91463, 88750, 86003, 83223, 80411, 77568, 74695, 71794, 68865, 65910, 62930, 59926, 56900, 53852, 50784,
47697, 44592, 41471, 38335, 35185, 32023, 28850, 25667, 22475, 19276, 16071, 12862, 9649, 6434, 3217, 0,
-3217, -6434, -9649, -12862, -16071, -19276, -22475, -25667, -28850, -32023, -35185, -38335, -41471, -44592, -47697, -50784,
-53852, -56900, -59926, -62930, -65910, -68865, -71794, -74695, -77568, -80411, -83223, -86003, -88750, -91463, -94141, -96782,
-99386, -101951, -104476, -106961, -109404, -111804, -114161, -116473, -118739, -120958, -123129, -125251, -127323, -129344, -131071, -131071,
-131071, -129344, -127323, -125251, -123129, -120958, -118739, -116473, -114161, -111804, -109404, -106961, -104476, -101951, -99386, -96782,
-94141, -91463, -88750, -86003, -83223, -80411, -77568, -74695, -71794, -68865, -65910, -62930, -59926, -56900, -53852, -50784,
-47697, -44592, -41471, -38335, -35185, -32023, -28850, -25667, -22475, -19276, -16071, -12862, -9649, -6434, -3217, 0,
0, 3217, 6434, 9649, 12862, 16071, 19276, 22475, 25667, 28850, 32023, 35185, 38335, 41471, 44592, 47697,
50784, 53852, 56900, 59926, 62930, 65910, 68865, 71794, 74695, 77568, 80411, 83223, 86003, 88750, 91463, 94141,
96782, 99386, 101951, 104476, 106961, 109404, 111804, 114161, 116473, 118739, 120958, 123129, 125251, 127323, 129344, 131071,
131071, 129344, 127323, 125251, 123129, 120958, 118739, 116473, 114161, 111804, 109404, 106961, 104476, 101951, 99386, 96782
};

void EjecutarGeneradorFunciones(void) {
    unsigned int32 acumuladorFase = 0;
    unsigned int32 incrementoFase = (unsigned int32)((configGen.frecuenciaHz * 16777216.0f) / 48000.0f);

    while(g_sistemaEjecutando) {
        acumuladorFase += incrementoFase;
        unsigned int8 indiceTabla = (unsigned int8)(acumuladorFase >> 16);

        int32 muestraGenerada = 0;

        if(configGen.tipoOnda == 0) {
            muestraGenerada = (int32)((float)tablaSeno18[indiceTabla] * configGen.ganancia);
        } else {
            muestraGenerada = (indiceTabla < 128) ? (int32)(131071.0f * configGen.ganancia) : (int32)(-131071.0f * configGen.ganancia);
        }

        EscribirDACExterno_DCI(muestraGenerada, 0);

        if(kbhit(UART_PIC18)) {
            unsigned char cmdCheck = fgetc(UART_PIC18);
            if(cmdCheck == CMD_STOP) {
                g_sistemaEjecutando = 0;
                EscribirDACExterno_DCI(0, 0);
                break;
            }
        }
    }
    fprintf(UART_PIC18, "ACK:GENERADOR_OFF\n");
}

// ==========================================
// 14. BUCLE PRINCIPAL
// ==========================================
void main(void) {
    InicializarSistemaCalibracion();
    delay_ms(100);

    while(TRUE) {
        if(kbhit(UART_PIC18)) {
            unsigned char comando = fgetc(UART_PIC18);

            if (comando == CMD_STOP) {
                g_sistemaEjecutando = 0;
                configGen.activo = 0;
                EscribirDACExterno_DCI(0, 0);
                fprintf(UART_PIC18, "ACK:STOP_OK\n");
            }
            else if (comando == CMD_START) {
                g_sistemaEjecutando = 1;
                fprintf(UART_PIC18, "ACK:START_OK\n");
            }
            else {
                switch (comando) {
                    case CMD_SET_MODO_THD:
                        EjecutarAnalisisDistorsion();
                        break;
                    case CMD_SET_MODO_BAND:
                        EjecutarSweepBandwidth();
                        break;
                    case CMD_Damping:
                        EjecutarDampingFactor();
                        break;
                    case CMD_TS_PARAMS:
                        EjecutarParametrosTS();
                        break;
                    case CMD_GENERADOR:
                        configGen.activo = 1;
                        EjecutarGeneradorFunciones();
                        break;
                    case CMD_SLEW_RATE:
                        EjecutarSlewRate();
                        break;
                    case CMD_POTENCIAS:
                        EjecutarPotenciasAudio();
                        break;
                    default:
                        fprintf(UART_PIC18, "ERR:CMD_UNKNOWN\n");
                        break;
                }
            }
        }
    }
}

- Revisión 1.4: La IA me había dejado los ADC / DAC de 18bits limitados a 16bits 🤦🏻‍♂️
- Revisión 1.5: La IA corrigió el uso de los 18bits de los ADC / DAC, pero olvidó el hecho de que tenemos una RAM externa y la compilación quedaba desbordada.
- Revisión 1.6: La IA No sabía como hablar con el puerto DCI del dsPIC, tuve que darle las instrucciones internas del driver 30F4013.h

Nota importante, tanto los archivos como el código cambian sin previo aviso para mejorar u ocultar evidencia de metidas de pata, se debe estar al pendiente con la revisión 😁
 
Última edición:
Atrás
Arriba