A continuación encontrarás un bosquejo en español de la solución, cubriendo la descripción conceptual, el diagrama de bloques, los cálculos básicos de SNR y distorsión, y un ejemplo de código (solo a modo de referencia) para cada microcontrolador ESP32 usando Arduino IDE. Se han dividido las secciones en (A) Adquisición y Compresión, (B) Transmisión, (C) Recepción y Descompresión, y (D) DAC y Salida al Parlante. Finalmente, se incluyen tablas con parámetros ajustables y las fórmulas aproximadas de cuantización y mu-law.
A. Esquema general del sistema
Diagram
1. Descripción de bloques
Entradas analógicas (0-3.3V): Se recomienda ajustar la fuente de señal o micrófono para no superar 3.3V y evitar saturar el ADC.
ADC del ESP32: Se configura con atenuación para maximizar la linealidad en el rango 0-3.3V (en el ESP32, la atenuación de 0 dB suele ser suficiente si cuidamos la ganancia anterior).
Compresión μ-law: Reducción de la resolución efectiva a 8 bits utilizando una curva logarítmica (μ=128).
Transmisión serial síncrona: Se utilizan pines UART TX, RX y se añade una línea extra para el clock. Aunque UART típicamente no usa clock externo, se puede generar una señal de sincronización para alinear muestreos o bytes.
Recepción y Descompresión: El segundo ESP32 recibe los bytes comprimidos, aplica la ley inversa y reconstruye la amplitud en un buffer interno (12 o 16 bits en software), para luego enviar a un DAC de 8 bits.
Filtro pasabajos: Suaviza los escalones del DAC y reconstruye la señal analógica (se dimensiona para frecuencias hasta ~4 kHz * 2.5 = 10 kHz de margen).
Driver de parlante: El ESP32 no puede entregar mucha corriente directamente; se recomienda un pequeño amplificador de audio o un transistor en configuración push-pull.
B. Adquisición y Compresión1. ADC del ESP32
Resolución nominal: 12 bits.
Resolución efectiva: Alrededor de 10-11 bits típicamente, debido a no linealidad interna y ruido de conversión.
Rango de entrada recomendado: 0-3.3 V.
Atenuación: 0 dB para rango de 0-3.3 V (en algunos modelos de ESP32 se puede usar 2.5 dB o 6 dB según sea necesario).
Error de cuantización teórico:
Para un ADC ideal de 12 bits y rango ( 0 - 3.3 , \mathrm{V} ), el paso es ( \Delta = \frac{3.3\text{V}}{2^{12}} \approx 0.8 \text{ mV} ).
SQNR (Signal-to-Quantization Noise Ratio) ideal en dB para un convertidor de (n) bits:
En la práctica, la linealidad del ESP32 reduce algo este valor.
2. Compresión μ-law (μ=128)
Ley μ en su forma continua:
F(x)=sgn(x)ln(1+μ)ln(1+μ∣x∣)
En la práctica, se discretiza a 8 bits: se genera una tabla de 256 valores para la compresión y otra para la descompresión en software.
Objetivo: Reducir el ruido perceptual en señales de voz y aprovechar mejor los pocos bits en la parte baja de la amplitud.
C. Transmisión digital UART + Sincronización
Protocolo
Se define una trama básica: cada byte (8 bits) corresponde a la muestra comprimida μ-law.
Velocidad de baud recomendada (por ejemplo, 250 kBaud) con una línea adicional de reloj o "sync" a 16 kHz que marque el inicio de cada muestra.
Distancia entre microcontroladores = 5 cm: es muy corta, un simple par UTP con nivel TTL 3.3 V y quizás una resistencia pull-up en la línea de reloj basta.
Sincronización
Puede implementarse un pin digital que cambie de estado cada vez que se envíe un nuevo byte comprimido.
El segundo MCU detecta ese flanco para alinear la lectura UART o para iniciar una interrupción.
Conexión física (ejemplo de pines recomendados, ajustable según ESP32):
ESP32 #1 (Transmisor)
GPIO17 → TX UART
GPIO16 ← RX UART (no necesariamente usado si sólo emite).
GPIO4 → Clock de sincronización (pulso por byte).
GND común.
ESP32 #2 (Receptor)
GPIO17 ← RX UART
GPIO16 → TX UART (solo si se requiere respuesta)
GPIO5 ← Clock de sincronización
GND común.
D. Recepción, Descompresión y DAC
Descompresión
Se utiliza la tabla inversa de µ-law. Cada byte (8 bits) se expande a 12 o 16 bits en software (p. ej., en variable uint16_t).
Se implementa un filtro digital de décimas si se quiere reducir ruido extra.
DAC
El ESP32 carece de DAC de alta resolución en muchos modelos, típicamente 8 bits en dos canales (GPIO25 y GPIO26).
Se escribe la amplitud reconstruida (recortada a 8 bits).
Se filtra de manera analógica para suavizar la señal escalonada.
Driver de parlante
Se recomienda un pequeño amplificador en puente (ej. una etapa push-pull con transistores o un amplificador en clase D si se quiere más eficiencia).
Parlante de 5 Ω requiere un control de corriente mayor de lo que el pin DAC puede entregar directamente.
E. Aspectos temporales y de muestreo
Frecuencia máxima de señal = 4 kHz
Según Nyquist, la frecuencia de muestreo mínima (f_s) debe ser > 8 kHz.
Para mayor margen y calidad, se podría muestrear a 16 kHz.
Con compresión a 8 bits, la tasa de bits es 16 kS/s * 8 bits = 128 kbps.
Diagrama de tiempos (simplificado)
Cada (\Delta t = 1 / f_s), generar interrupción de timer.
Leer ADC (12 bits) → Comprimir a 8 bits.
Transmitir 1 byte por UART.
Generar pulso en señal de sincronización.
El receptor detecta el pulso, lee el byte UART → Descomprime → Envía al DAC.
F. Ejemplo de Código (Arduino) – Transmisor
Nota: Este código es simplemente ilustrativo y puede requerir ajustes a la librería o a la implementación concreta de mu-law. Las funciones de compresión y la tabla deben crearse según la curva μ-law.
Cpp
/* Ejemplo Transmisor PCM con μ-law (ESP32) */#include<driver/adc.h>#include<HardwareSerial.h>#defineFS16000// Frecuencia de muestreo deseada#defineSYNC_PIN4// Pin de sincronización (salida)#defineADC_CHANNELADC1_CHANNEL_0 // GPIO36 en algunos ESP32HardwareSerial SerialPCM(2);// UART2// Tabla de compresión mu-law (256 valores)uint8_t muLawCompressTable[4096];// Ajustar según max. 12 bits si se desea// (Se genera offline o con una rutina setupMukawTables())hw_timer_t *timer =NULL;// Rutina de interrupción de muestreovoid IRAM_ATTR onTimer(){// 1. Leer ADC (12 bits)int raw =adc1_get_raw(ADC_CHANNEL);// 0-4095 (aprox. con atenuación=0)// 2. Comprimir a 8 bits mu-law// Suponiendo que "raw" se normaliza de 0 a 4095, se centra en +/-2048, etc.// Ejemplo pseudo-línea:uint8_t muValue = muLawCompressTable[ raw &0xFFF];// 3. Generar pulso de sincronizacióndigitalWrite(SYNC_PIN, HIGH);// 4. Enviar el byte por UART SerialPCM.write(muValue);// 5. Bajar pulsodigitalWrite(SYNC_PIN, LOW);}voidsetup(){// Config ADCadc1_config_width(ADC_WIDTH_BIT_12);adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_DB_0);// Config pin de sincronizaciónpinMode(SYNC_PIN, OUTPUT);digitalWrite(SYNC_PIN, LOW);// Config UART SerialPCM.begin(250000, SERIAL_8N1,-1,-1,false);// TX default pin GPIO17// (Inicializa tabla muLawCompressTable) => setupMuLawTables();// Config timer a FS = 16 kHz timer =timerBegin(0,80,true);// A 80 MHz / 80 = 1 MHz -> se cuentan 1 microseg = 1 tick// Queremos 16000 Hz => 1/16000 = 62.5us, => 62.5 tickstimerAttachInterrupt(timer,&onTimer,true);timerAlarmWrite(timer,62,true);timerAlarmEnable(timer);}voidloop(){// Hacer otras tareas}
Comentarios principales:
Se deja la muestra en 12 bits, se usa la tabla muLawCompressTable[] para obtener el valor 8 bits.
La línea de sincronización (GPIO4) genera un pulso cada vez que se lee/senvía una muestra.
El baudrate de 250000 es un ejemplo; se debe ajustar para que en ~62 µs se alcance a enviar 1 byte (1 start bit + 8 data + 1 stop = 10 bits → 10/250000=40 µs aprox.). Hay un margen de ~22 µs para overhead.
G. Ejemplo de Código (Arduino) – Receptor
Cpp
/* Ejemplo Receptor PCM con μ-law (ESP32) */#include<driver/dac.h>#include<HardwareSerial.h>#defineFS16000#defineSYNC_PIN5// Pin de entrada de sincronización#defineDAC_CHANNELDAC_CHANNEL_1 // DAC: GPIO25 en ESP32HardwareSerial SerialPCM(2);// UART2hw_timer_t *timer =NULL;volatileuint8_t lastSample =0;// Tabla inversa (para 8 bits a 12 o 16 bits) – de preferencia 256 valoresuint16_t muLawDecompressTable[256];void IRAM_ATTR onSync(){// Leer el byte disponible en UART (blocking o buffer)if(SerialPCM.available()){ lastSample = SerialPCM.read();}// Alternativa: se podría alinear la interrupción con un timer si queremos exactitud // de muestreo local. Depende del diseño final.}void IRAM_ATTR onTimer(){// Reconstruir muestra 12 bitsuint16_t expanded = muLawDecompressTable[lastSample];// 0..4095 (~12 bits)// Reducir a 8 bits para DACuint8_t dacValue = expanded >>4;// descartar 4 bitsdacWrite(DAC_CHANNEL_1, dacValue);}voidsetup(){// Config DACdacOutputEnable(DAC_CHANNEL_1);// Config UART SerialPCM.begin(250000, SERIAL_8N1,-1,-1,true);// "true" => Invertir pines si es necesario. Ajustar según tu ESP32.// Config pin de sincronizaciónpinMode(SYNC_PIN, INPUT_PULLDOWN);attachInterrupt(digitalPinToInterrupt(SYNC_PIN), onSync, RISING);// (Inicializa tabla muLawDecompressTable)// Config timer // Nota: si se quiere un playback sin dependencia exacta del pin SYNC, // se programa el timer a 16 kHz y en la callback onTimer() se lee "lastSample". // O se puede directamente emplear la interrupción onSync para doEverything. timer =timerBegin(1,80,true);timerAttachInterrupt(timer,&onTimer,true);timerAlarmWrite(timer,62,true);timerAlarmEnable(timer);}voidloop(){// Otras tareas}
Comentarios:
Se asume que llega un byte cada ~62 µs.
lastSample se actualiza de forma asíncrona.
onTimer() genera la salida de audio al DAC con la última muestra recibida.
Se puede combinar la lectura y la reproducción en la misma ISR si se desea sincronización estricta con el pulso.
H. Protección y Filtrado
Protección contra sobrevoltaje
Para la entrada ADC: un diodo de clamp + resistencia en serie (p.ej., 10 kΩ) para evitar picos > 3.3V.
Filtros analógicos
Antialias en la entrada del ADC: un filtro RC pasa-bajos con (f_c \approx 5\text{ kHz}) si la señal de interés es hasta 4 kHz.
Reconstrucción en la salida DAC: un filtro RC o activo (op-amp) con (f_c) algo mayor a 4 kHz (p.ej., 8-10 kHz).
I. Cálculos de la Frecuencia de Muestreo y Tasa de datos
Muestreo: (f_s = 16\text{ kHz}).
Bits por muestra: 8 bits (gracias a la compresión μ-law).
Tasa de datos: (16,000 \times 8 = 128 \text{ kbps}).
Tiempo por byte: (1/16,000 \approx 62.5,\mu s).
J. Relación Señal/Ruido de Cuantización (SQNR) y Distorsión μ-law
Con μ-law y señal de voz, la calidad percibida supera la simple cuantización lineal de 8 bits, especialmente en señales de bajo nivel.
Distorsión por compresión
La compresión introduce mayor error en amplitudes altas debido a la curva logarítmica; no obstante, aporta mejor relación señal/ruido en amplitudes bajas.
Normalmente aceptable para voz o señales de ancho de banda reducida (~300 Hz a 4 kHz).
K. Tabla de Parámetros Ajustables
Table
Parámetro
Valor/Recomendación
Observaciones
Frecuencia de Muestreo (fs)
16 kHz
Asegura ancho de banda hasta ~8 kHz (sobrado para voz a 4 kHz).
Atenuación ADC ESP32
0 dB (por defecto)
Ajustar si la señal de entrada excede 3.3V.
Bits finales (compresión)
8
μ-law.
μ (Factor de compresión)
128
Ajustable: 255 suele ser tambien frecuente.
Baud Rate UART
250 kBaud (mín)
Ajustar para no superar el tiempo de muestreo.
Filtro Antialias (RC)
~5 kHz
Depende de la señal de entrada.
Filtro Reconstrucción (RC)
~8-10 kHz
Para suavizar la salida a 8 bits.
Pin SYNC adicional
1 señal digital
Alinea muestras; se puede omitir con buffers FIFO.
Amplificador de salida
Transistor push-pull o Amp audio bajo consumo
Para parlante de 5 Ω.
L. Conclusiones y Siguientes Pasos
Integración final: Verificar que el timing sea estable. Usar osciloscopio para comprobar la señal ADC, la trama UART y el pulso de sincronía.
Ruido y distorsión: Ajustar filtro pasabajos y realizar pruebas de escucha con el parlante.
Optimizaciones:
Ajustar interrupciones y prioridad para no introducir jitter.
Usar buffers circulares (colas) y DMA en UART si se requiere robustez.
Usar un amplificador en la salida para el parlante (p. ej., un pequeño módulo clase D de 2-3 W).
Con esto se logra un sistema de transmisión PCM comprimida (μ-law) a baja distancia (~5 cm) entre dos ESP32, capturando hasta 4 kHz de audio y reproduciéndolo en el otro extremo. Esto cubre tanto los cálculos de muestreo, la comunicación serial, la compresión logarítmica y la reconstrucción de señal con DAC + filtro.
@copilot "module ALU_N_bits #(
parameter N = 8
)(
input wire [N-1:0] A,
input wire [N-1:0] B,
input wire [2:0] control, // Bus de control para seleccionar la operación
output wire [N-1:0] result,
output wire carry_borrow
);
wire [N-1:0] sum_res, and_res, or_res, xor_res, not_res, shl_res, shr_res;
wire sum_carry;
// Instanciamos los módulos de operaciones
add_sub #(.N(N)) add_sub_inst (
.A(A),
.B(B),
.sel(control[0]), // Si control[0] = 0 -> Suma, si es 1 -> Resta
.R(sum_res),
.carry_borrow(sum_carry)
);
and_nbits #(.N(N)) and_inst (
.A(A),
.B(B),
.Y(and_res)
);
or_nbits #(.N(N)) or_inst (
.A(A),
.B(B),
.Y(or_res)
);
xor_nbits #(.N(N)) xor_inst (
.A(A),
.B(B),
.Y(xor_res)
);
not_nbits #(.N(N)) not_inst (
.A(A),
.Y(not_res)
);
shift_left_nbits #(.N(N)) shl_inst (
.A(A),
.Y(shl_res)
);
shift_right_nbits #(.N(N)) shr_inst (
.A(A),
.Y(shr_res)
);
// Multiplexor para seleccionar la operación
assign result = (control == 3'b000) ? sum_res : // Suma o Resta
(control == 3'b001) ? and_res : // AND
(control == 3'b010) ? or_res : // OR
(control == 3'b011) ? xor_res : // XOR
(control == 3'b100) ? not_res : // NOT (Solo A)
(control == 3'b101) ? shl_res : // Shift Left
(control == 3'b110) ? shr_res : // Shift Right
{N{1'b0}}; // Default: 0
// El carry/borrow solo tiene sentido en suma/resta
assign carry_borrow = (control == 3'b000) ? sum_carry : 1'b0;
endmodule
"
Puedes hacer una construcción donde muestren cómo los módulos están interconectados. Además, realiza una breve explicación de cómo funciona y las consideraciones de diseño e implementación que tendrías en cuenta si fueras a diseñar el módulo.
Let's create a diagram to illustrate how the modules in the ALU are interconnected and provide a brief explanation of how the ALU functions, along with some design and implementation considerations.
Interconnection Diagram
Here is a simplified block diagram of the ALU_N_bits module, showing the interconnections between the various operation modules and the multiplexer used to select the output based on the control signal:
graph TD;
A[Input A $N-1:0$] --> add_sub_inst;
B[Input B $N-1:0$] --> add_sub_inst;
add_sub_inst -->|Sum Result| sum_res;
add_sub_inst -->|Carry| sum_carry;
A --> and_inst;
B --> and_inst;
and_inst --> |AND Result| and_res;
A --> or_inst;
B --> or_inst;
or_inst --> |OR Result| or_res;
A --> xor_inst;
B --> xor_inst;
xor_inst --> |XOR Result| xor_res;
A --> not_inst;
not_inst --> |NOT Result| not_res;
A --> shl_inst;
shl_inst --> |Shift Left Result| shl_res;
A --> shr_inst;
shr_inst --> |Shift Right Result| shr_res;
control[Control $2:0$] --> mux;
sum_res --> mux;
and_res --> mux;
or_res --> mux;
xor_res --> mux;
not_res --> mux;
shl_res --> mux;
shr_res --> mux;
mux --> |"Result $N-1:0$"| result;
sum_carry --> |Carry/Borrow| carry_borrow;
control --> carry_borrow;
Functionality and Explanation
Inputs and Outputs:
The ALU takes two N-bit wide inputs, A and B, and a 3-bit control input to determine the operation to perform.
It produces an N-bit wide result and a carry/borrow flag for arithmetic operations (add/subtract).
Operation Modules:
The add_sub module performs addition or subtraction between A and B based on the least significant bit of the control signal, control[0].
Logical operations include AND, OR, and XOR, handled by corresponding modules.
The not_nbits module complements the bits of A.
Shift operations (left and right) manipulate the bit positions of A.
Multiplexer (MUX):
A multiplexer selects which operation's result to output based on the control signal. Each control pattern corresponds to a different operation:
3'b000: Addition or Subtraction
3'b001: AND
3'b010: OR
3'b011: XOR
3'b100: NOT
3'b101: Shift Left
3'b110: Shift Right
Carry/Borrow:
The carry/borrow is only relevant for addition or subtraction, so it is passed through only when the control is for the sum operation (3'b000).
Design and Implementation Considerations
Parameterizability: The use of a parameter $N$ makes the module scalable for different bit-width operations.
Modularity: Each operation is modularized, allowing easy updates or enhancements without affecting the entire ALU.
Default Output: When the control signal does not match an operation, the ALU outputs a default value of zero.
Timing: Consideration for timing constraints, especially for larger bit-widths, as some operations might become timing-critical.
Resource Usage: Balance between resource usage (e.g., LUTs in FPGAs) and speed, particularly in the context of target technology (ASIC vs. FPGA).
Testability: Ensure thorough testing for each individual module as well as the overall ALU to verify correct functionality under various conditions.
General Purpose Thick Film Standard Power
and High-Power Chip Resistor
47 kOhms ±5% 0.125W, 1/8W Chip Resistor 0805 (2012 Metric) Automotive AEC-Q200 Thick Film
Features:
- RMCF – standard power ratings
- RMCP – high power ratings
- Nickel barrier terminations standard
- Power derating from 100% at 70ºC to zero at +155ºC
- RoHS compliant, REACH compliant, and halogen free
- AEC-Q200 compliant
A momentary or non-latching switch which causes a temporary change in the state of an electrical circuit only while the switch is physically actuated. SWITCH TACTILE SPST-NO 0.05A 24V
johanrro
copilot
-
Inputs and Outputs:
- The ALU takes two N-bit wide inputs, A and B, and a 3-bit control input to determine the operation to perform.
- It produces an N-bit wide result and a carry/borrow flag for arithmetic operations (add/subtract).
-
Operation Modules:
- The add_sub module performs addition or subtraction between A and B based on the least significant bit of the control signal,
- Logical operations include AND, OR, and XOR, handled by corresponding modules.
- The not_nbits module complements the bits of A.
- Shift operations (left and right) manipulate the bit positions of A.
-
Multiplexer (MUX):
- A multiplexer selects which operation's result to output based on the control signal. Each control pattern corresponds to a different operation:
-
Carry/Borrow:
- The carry/borrow is only relevant for addition or subtraction, so it is passed through only when the control is for the sum operation (
Design and Implementation Considerationscontrol[0].3'b000: Addition or Subtraction3'b001: AND3'b010: OR3'b011: XOR3'b100: NOT3'b101: Shift Left3'b110: Shift Right3'b000).