Resumen | Este codelab fue creado para lograr comprender una posible implementación de algoritmos de adquisición de señales: muestreo y ventanas de tiempo en una ESP32. Se espera que usted al finalizar esté en capacidad de:
|
Fecha de Creación: | 2024/03/01 |
Última Actualización: | 2024/03/01 |
Requisitos Previos: | |
Adaptado de: | |
Referencias: | |
Escrito por: | Fredy Segura-Quijano |
La adquisición de datos en sistemas embebidos como la ESP32 implica la conversión de señales analógicas del entorno físico en datos digitales que pueden ser procesados y analizados. Este proceso consta principalmente de dos etapas: muestreo y uso de ventanas de tiempo. El muestreo es la captura de valores discretos de una señal continua a intervalos regulares. En el contexto de la ESP32, esto se realiza utilizando el ADC (Convertidor Analógico-Digital) integrado. Las ventanas de tiempo se utilizan para segmentar la señal en bloques manejables, facilitando el análisis posterior, como la transformación de Fourier para el análisis espectral u otras técnicas.
Para implementar el muestreo en una ESP32, primero se configura el conversor análogo-digital (ADC) para capturar la señal analógica. La ESP32 cuenta con varios canales ADC de 12 bits que pueden leer señales analógicas de 0 a 3.3V. Aquí hay un ejemplo de cómo configurar y realizar el muestreo en la ESP32 utilizando el entorno de desarrollo Arduino:
#include <driver/adc.h>
#define ADC_CHANNEL ADC1_CHANNEL_0 // GPIO36
#define SAMPLE_RATE 1000 // Frecuencia de muestreo en Hz
#define BUFFER_SIZE 1000 // Tamaño del buffer para almacenar muestras
int16_t samples[BUFFER_SIZE];
unsigned long lastSampleTime = 0;
unsigned long sampleInterval = 1000000 / SAMPLE_RATE; // Intervalo de muestreo en microsegundos
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_DB_0);
}
void loop() {
if (micros() - lastSampleTime >= sampleInterval) {
lastSampleTime = micros();
int16_t sample = adc1_get_raw(ADC_CHANNEL);
storeSample(sample);
}
}
void storeSample(int16_t sample) {
static int index = 0;
samples[index++] = sample;
if (index == BUFFER_SIZE) {
processSamples(samples, BUFFER_SIZE);
index = 0;
}
}
void processSamples(int16_t* samples, int size) {
// Procesar las muestras, por ejemplo, aplicar una ventana y realizar análisis espectral
}
Las ventanas de tiempo se utilizan para dividir las señales muestreadas en segmentos más pequeños, lo que facilita el análisis frecuencial y temporal. En la implementación anterior, las muestras se almacenan en un buffer y, una vez que el buffer está lleno, se procesan las muestras. Aplicar una ventana implica multiplicar cada muestra por una función de ventana, como la ventana de Hamming, que reduce los efectos de borde. A continuación se presenta un ejemplo para implementar una ventana de Hamming:
void applyHammingWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.54 - 0.46 * cos(2 * PI * i / (size - 1)));
}
}
Implementar el muestreo y las ventanas de tiempo en ESP32 permite el análisis detallado de señales en tiempo real. Esto es útil en muchas aplicaciones, como el monitoreo de condiciones ambientales, la detección de fallos en sistemas industriales y el análisis de señales biomédicas. Por ejemplo, en un sistema de monitoreo de calidad del aire, la ESP32 puede muestrear continuamente señales de sensores de gas, aplicar ventanas de tiempo y realizar análisis espectral para detectar la presencia de contaminantes específicos.
La ESP32, con su capacidad de procesamiento y múltiples canales ADC, es ideal para aplicaciones de adquisición de datos en tiempo real. Sin embargo, uno de los desafíos es gestionar eficientemente el almacenamiento y procesamiento de grandes volúmenes de datos con los recursos limitados del Sistema Embebido. Optimizar el código para reducir el uso de memoria y procesar los datos de manera eficiente es crucial para lograr un rendimiento óptimo en aplicaciones de tiempo real.
Existen varios tipos de ventanas que se pueden utilizar en el procesamiento de señales para minimizar los efectos de borde y mejorar el análisis espectral. Algunas de las ventanas más comunes incluyen la ventana de Hamming, ventana de Hanning, ventana Rectangular, ventana Blackman y ventana de Bartlett entre otras técnicas.
A continuación, se presentan ejemplos de cómo implementar estas ventanas en la ESP32 usando el entorno de desarrollo Arduino.
La ventana de Hamming es una función que se utiliza para suavizar las transiciones en los bordes de un segmento de señal. Tiene la forma:
donde 𝑁 es el tamaño de la ventana y 𝑛 es el índice de la muestra.
void applyHammingWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.54 - 0.46 * cos(2 * PI * i / (size - 1)));
}
}
Esta ventana se puede usar cuando se desea reducir los picos secundarios (lóbulos laterales) en el espectro de la señal, ya que esta ventana proporciona una buena atenuación de estos picos sin afectar demasiado la resolución frecuencial. Es adecuada para el análisis espectral de señales donde los picos secundarios pueden interferir con la detección de componentes frecuenciales cercanas.
La ventana de Hanning, también conocida como ventana de Hann, es similar a la ventana de Hamming, pero con una fórmula ligeramente diferente:
void applyHanningWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.5 - 0.5 * cos(2 * PI * i / (size - 1)));
}
}
Esta ventana se puede usar cuando se busca un compromiso entre la atenuación de los picos secundarios y la resolución frecuencial. Ideal para aplicaciones donde los picos secundarios no deben ser demasiado prominentes y se necesita una buena resolución en frecuencia.
La ventana rectangular es la más simple y no aplica ningún peso adicional a las muestras.
para todos los n.
void applyRectangularWindow(int16_t* samples, int size) {
// No se necesita realizar ninguna operación, ya que es una ventana uniforme
}
Esta ventana se puede usar cuando se necesita la máxima resolución temporal y la señal ya está libre de efectos de borde significativos. Es útil en aplicaciones donde la señal es estacionaria y no hay transiciones bruscas que puedan introducir artefactos en el análisis espectral.
La ventana de Blackman es una función que proporciona una atenuación muy alta de los picos secundarios, a costa de una menor resolución frecuencial. Su implementación se basa en la siguiente ecuación:
void applyBlackmanWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.42 - 0.5 * cos(2 * PI * i / (size - 1)) + 0.08 * cos(4 * PI * i / (size - 1)));
}
}
Esta ventana se usa cuando la prioridad es minimizar los picos secundarios en el espectro, aunque eso implique una pérdida en la resolución de frecuencia. Es adecuada para aplicaciones donde los picos secundarios pueden causar interferencias significativas y se necesita una señal muy limpia.
La ventana de Bartlett, o ventana triangular, tiene la forma de un triángulo:
void applyBartlettWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (2.0 / (size - 1) * ((size - 1) / 2.0 - abs(i - (size - 1) / 2.0)));
}
}
Esta ventana se usa cuando se busca una ventana simple y se necesita un compromiso entre la resolución frecuencial y la atenuación de los picos secundarios. Es útil en aplicaciones donde se requiere una buena representación del contenido frecuencial con un método de implementación sencillo.
Ventana de Hamming: Buena atenuación de picos secundarios, ideal para análisis espectral donde estos picos pueden interferir.
Ventana de Hanning: Equilibrio entre resolución frecuencial y atenuación de picos secundarios, buena para un análisis general.
Ventana Rectangular: Máxima resolución temporal, útil para señales estacionarias sin efectos de borde significativos.
Ventana Blackman: Excelente atenuación de picos secundarios, adecuada para señales donde la limpieza espectral es crucial.
Ventana de Bartlett: Compromiso entre resolución y atenuación, útil para aplicaciones con requisitos simples y eficientes.
La elección de la ventana depende del balance deseado entre la resolución temporal y frecuencial, así como la necesidad de minimizar los picos secundarios en el espectro.
A continuación se presenta un ejemplo completo que incluye la configuración de la ESP32 para la adquisición de datos y la aplicación de diferentes ventanas:
#include <driver/adc.h>
#define ADC_CHANNEL ADC1_CHANNEL_0 // GPIO36
#define SAMPLE_RATE 1000 // Frecuencia de muestreo en Hz
#define BUFFER_SIZE 1000 // Tamaño del buffer para almacenar muestras
int16_t samples[BUFFER_SIZE];
unsigned long lastSampleTime = 0;
unsigned long sampleInterval = 1000000 / SAMPLE_RATE; // Intervalo de muestreo en microsegundos
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_DB_0);
}
void loop() {
if (micros() - lastSampleTime >= sampleInterval) {
lastSampleTime = micros();
int16_t sample = adc1_get_raw(ADC_CHANNEL);
storeSample(sample);
}
}
void storeSample(int16_t sample) {
static int index = 0;
samples[index++] = sample;
if (index == BUFFER_SIZE) {
applyWindow(samples, BUFFER_SIZE);
processSamples(samples, BUFFER_SIZE);
index = 0;
}
}
void applyWindow(int16_t* samples, int size) {
applyHanningWindow(samples, size);
// Cambia la función llamada aquí para usar diferentes ventanas
}
void applyHanningWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.5 - 0.5 * cos(2 * PI * i / (size - 1)));
}
}
void processSamples(int16_t* samples, int size) {
// Procesar las muestras, por ejemplo, realizar análisis espectral
// Aquí se podría aplicar una Transformada de Fourier, por ejemplo.
}
#include <driver/adc.h>
#define ADC_CHANNEL ADC1_CHANNEL_0 // GPIO36
#define SAMPLE_RATE 1000 // Frecuencia de muestreo en Hz
#define BUFFER_SIZE 1000 // Tamaño del buffer para almacenar muestras
int16_t samples[BUFFER_SIZE];
unsigned long lastSampleTime = 0;
unsigned long sampleInterval = 1000000 / SAMPLE_RATE; // Intervalo de muestreo en microsegundos
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_DB_0);
}
void loop() {
if (micros() - lastSampleTime >= sampleInterval) {
lastSampleTime = micros();
int16_t sample = adc1_get_raw(ADC_CHANNEL);
storeSample(sample);
}
}
void storeSample(int16_t sample) {
static int index = 0;
samples[index++] = sample;
if (index == BUFFER_SIZE) {
applyWindow(samples, BUFFER_SIZE);
processSamples(samples, BUFFER_SIZE);
index = 0;
}
}
void applyWindow(int16_t* samples, int size) {
applyHanningWindow(samples, size);
// Cambia la función llamada aquí para usar diferentes ventanas
}
void applyHanningWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.5 - 0.5 * cos(2 * PI * i / (size - 1)));
}
}
void processSamples(int16_t* samples, int size) {
// Procesar las muestras, por ejemplo, realizar análisis espectral
// Aquí se podría aplicar una Transformada de Fourier, por ejemplo.
}
La optimización de las ventanas de tiempo para el análisis de señales continuas es un aspecto crucial en el procesamiento de señales, ya que puede afectar significativamente la precisión y la calidad del análisis espectral y temporal. A continuación, se describen algunos conceptos y técnicas clave para optimizar las ventanas de tiempo.
1. Selección del Tipo de Ventana: La elección del tipo de ventana es fundamental. Diferentes ventanas tienen diferentes propiedades que pueden influir en el análisis de la señal:
2. Tamaño de la Ventana: El tamaño de la ventana es otro factor crítico. El tamaño afecta la resolución temporal y frecuencial del análisis:
Un enfoque común es utilizar la Transformada de Fourier de Ventana (STFT), que aplica una ventana móvil a la señal y calcula la Transformada de Fourier en cada segmento. La elección del tamaño de la ventana depende de las características de la señal y del análisis específico que se desee realizar.
3. Superposición de Ventanas: Para mejorar la continuidad entre las ventanas y evitar pérdida de información, se puede aplicar una superposición de ventanas. Este método implica que las ventanas adyacentes se solapen parcialmente, lo que ayuda a suavizar las transiciones y proporciona un análisis más preciso:
A continuación se presenta un ejemplo de cómo implementar estas optimizaciones en ESP32:
#include <driver/adc.h>
#define ADC_CHANNEL ADC1_CHANNEL_0 // GPIO36
#define SAMPLE_RATE 1000 // Frecuencia de muestreo en Hz
#define BUFFER_SIZE 256 // Tamaño del buffer para almacenar muestras
#define OVERLAP 128 // Tamaño del solapamiento (50%)
int16_t samples[BUFFER_SIZE + OVERLAP];
unsigned long lastSampleTime = 0;
unsigned long sampleInterval = 1000000 / SAMPLE_RATE; // Intervalo de muestreo en microsegundos
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_DB_0);
}
void loop() {
static int index = 0;
if (micros() - lastSampleTime >= sampleInterval) {
lastSampleTime = micros();
int16_t sample = adc1_get_raw(ADC_CHANNEL);
storeSample(sample, index++);
if (index == BUFFER_SIZE) {
applyHanningWindow(samples, BUFFER_SIZE);
processSamples(samples, BUFFER_SIZE);
index = OVERLAP;
// Copia las últimas muestras solapadas al inicio del buffer
memmove(samples, samples + BUFFER_SIZE - OVERLAP, OVERLAP * sizeof(int16_t));
}
}
}
void storeSample(int16_t sample, int index) {
samples[index] = sample;
}
void applyHanningWindow(int16_t* samples, int size) {
for (int i = 0; i < size; i++) {
samples[i] = samples[i] * (0.5 - 0.5 * cos(2 * PI * i / (size - 1)));
}
}
void processSamples(int16_t* samples, int size) {
// Procesar las muestras, por ejemplo, realizar análisis espectral
// Aquí se podría aplicar una Transformada de Fourier, por ejemplo.
}
Se deben tener en cuenta algunas consideraciones finales teniendo en cuenta que el objetivo es lograr una buena adquisición de señales en un sistema con pocos recursos computacionales.
La optimización de las ventanas de tiempo implica seleccionar el tipo adecuado de ventana, ajustar su tamaño, y aplicar la superposición correcta para obtener un análisis preciso y eficiente de señales continuas en sistemas embebidos como la ESP32.
A continuación tienes realimentación a las preguntas de comprensión. Tus respuestas no quedan almacenadas, solo se busca hacer una reflexión sobre la lectura.