Grabador de apnea

  • Rubén Bargueño Prieto
  • Iván Bernal Blanco
  • Robert Ray López Villavicencio

En este proyecto se trata de crear un dispositivo que grabe el ruido ambiente por la noche y así poder detectar la apnea del sueño. El proyecto se basa en una Arduino Mega y otros componentes mencionados más adelante.

MATERIALES

ComponentePrecio Unidad(€)UnidadesTotal(€)Gastos de envío(€)Total con envío(€)
Arduino Mega 256014,24114,24014,24
Proto Shield2,4712,471,894,36
Adaptador Micro SD0,5710,570,110,68
Tarjeta Micro SD 2GB1,911,901,9
Reloj RTC DS32311,311,30,712,01
Micrófono MAX98141,4611,460,72,16
Power Bank1,7411,741,873,61
Batería Ion Litio 3.7v3,5613,5603,56
Amp. Audio NS80022,3812,3802,38
Micro Interruptor0,0820,1600,16
Micro Botón0,0820,1600,16
Altavoz 0,5w1,111,101,1
Pulsadores Momentáneos0,6531,9501,95
Conector USB0,1910,1900,19
 Leds 5v0,2820,5600,56
Total39,02
Tabla de componentes y costes

DIAGRAMA DE CONEXIÓN

El conector USB estaría conectado en la misma fila que los pines GND y 5v, este conector se usará solo si se quiere alimentar el circuito de otro modo que no sea directamente a la placa Arduino Mega.

Conexiones detalladas

El Arduino MEGA 2560 se conecta a las dos primeras líneas de la protoboard mediante el pin GND (tierra) y 5V (corriente).

El adaptador microSD posee siete pines:

  • Los dos primeros se conectan a la protoboard en tierra y corriente. 
  • Los cuatro siguientes se conectan al arduino con los pines digitales de entrada y salida.
  • El último pin se conecta al Arduino con un pin digital de entrada y salida PWM.

El módulo de reloj de tiempo real (DS3231 RTC) tiene cuatro pines:

  • Los dos primeros se conectan al Arduino mediante sus pines correspondientes (los de interrupciones externas).
  • Los dos siguientes se conectan a la protoboard en tierra y corriente.

El micrófono MAX9814 dispone de tres pines:

  • El primero se conecta al Arduino mediante un pin analógico de entrada.
  • Los dos siguientes se conectan a la protoboard en tierra y corriente.

El amplificador de audio (NS8002) tiene seis conexiones:

  • El primero se conecta al Arduino con un pin digital de entrada y salida PWM.
  • El segundo se conecta al Arduino con un pin digital de entrada y salida.
  • Los dos siguientes se conectan a la protoboard en tierra y corriente.
  • Los dos últimos se conectan al altavoz.

Los dos botones (borrar y play) disponen de dos conexiones cada uno:

  • Poseen una conexión a tierra y otra al Arduino mediante un pin digital de entrada y salida PWM.

El interruptor (escucha) posee tres conexiones: 

  • Posee una conexión a tierra, otra a corriente y otra al Arduino mediante un pin digital de entrada y salida PWM.

Los dos leds disponen de dos conexiones cada uno:

  • Poseen una conexión a tierra y otra al Arduino mediante un pin digital de entrada y salida PWM.

IMPLEMENTACIÓN DEL SOFTWARE

Para el desarrollo de este proyecto se ha utilizado como principal recurso de software la librería TMRpcm de Arduino cuyo principal cometido es la reproducción de audio desde una tarjeta SD utilizando el módulo serial. Está principalmente diseñada para funcionar con tarjetas de memoria SD y reproductores de audio integrados. Como se ha mencionado, su principal función es la reproducción de audio y el grabado del mismo en la memoria mediante el uso del formato .WAV, que pese a ser más pesado que otros formatos, es el que soporta esta librería. Estos archivos deben cumplir ciertas especificaciones y resolución de bits concretos. 
Su mayor limitación es la duración de audio que se puede grabar debido a que depende únicamente del tamaño de la tarjeta SD que se utilice. 
Para este proyecto se han utilizado muchas de las funcionalidades que nos ofrece, las más importantes se explicarán a continuación.
stopPlayback() se encarga de detener la reproducción del archivo de audio poniendo la variable correspondiente al estado de la reproducción (playing) a cero.

createWavTemplate() es la función encargada de la creación del archivo .WAV donde se grabará el audio.


Por último se comentará la función startRecording() que se activa cuando se pulsa el botón de grabación. Esta función coloca el puntero en el Byte número 44 donde comenzará a grabarse el archivo. Una vez está todo dispuesto, la variable recording cambia su valor para indicar al micrófono que debe empezar a captar audio y a al dispositivo que debe guardar este audio en el archivo creado. Una vez se ha terminado de grabar, o bien porque se ha detenido el ruido, o el dispositivo se ha desconectado, la variable se pone a cero y deja de grabar mediante la función stopRecording(). El resultado final es un archivo .WAV en el que se encuentra el audio recogido durante la noche.

Código del archivo GrabdorApneaNoche.ino
// ----------------------------------------------------
//
//
// ----------------------------------------------------

#include <SD.h> // La SD Card va por el bus SPI.
#include <Wire.h> // El RTC va por el bus I2C.
#include <TMRpcm.h>     

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

TMRpcm tmrpcm;

File rec;

const unsigned int darBufSize = 128;
const unsigned int totalBuf = darBufSize * 8;
const unsigned int bufSplit_1 = darBufSize;
const unsigned int bufSplit_2 = darBufSize * 2;
const unsigned int bufSplit_3 = darBufSize * 3;
const unsigned int bufSplit_4 = darBufSize * 4;
const unsigned int bufSplit_5 = darBufSize * 5;
const unsigned int bufSplit_6 = darBufSize * 6;
const unsigned int bufSplit_7 = darBufSize * 7;

const int chipSelect = 4;
unsigned long fileSize = 0L;
unsigned long waveChunk = 16;
unsigned int waveType = 1;
unsigned int numChannels = 1;

char nombrefich_graba[13] ="HH-MI-SS.wav";

//----------- FRECUENCIA DE MUESTREO -------------------
unsigned long sampleRate = 16000;
//----------- FRECUENCIA DE MUESTREO -------------------

unsigned long bytesPerSec = sampleRate;
unsigned int blockAlign = 1;
unsigned int bitsPerSample = 8;
unsigned long dataSize = 0L;
unsigned long recByteCount = 0L;
unsigned long recByteSaved = 0L;
unsigned long playByteCount = 0L;  // RBP. 24/10/2023
unsigned long playByteRead = 0L; // RBP. 24/10/2023
unsigned long ultSonido; // RBP. 11/11/2023
const int btnEscucha =6; 
int leidos;
unsigned int bufCuenta;
int val_btnEscucha; 
int val_btnEscucha_ant=HIGH; 
const int btnBorra =3; 
int val_btnBorra; 
int val_btnBorra_ant=HIGH; 
const int btnPlay =2; 
int val_btnPlay; 
int val_btnPlay_ant=HIGH; 
int pinMute = 8; // RBP 11/11/2023
const int ledEscucha = 5; // RBP 30/10/2023
const int ledGraba = 7; // RBP 30/10/2023
const int altavoz = 9; 
int modoPlay = 0;
int modoGraba = false;
int modoGraba_ant = false;
int modoEscucha = false;
byte buf00[darBufSize]; // buffer array 1
byte buf01[darBufSize]; // buffer array 2
byte buf02[darBufSize]; // buffer array 3
byte buf03[darBufSize]; // buffer array 4
byte buf04[darBufSize]; // buffer array 5
byte buf05[darBufSize]; // buffer array 6
byte buf06[darBufSize]; // buffer array 7
byte buf07[darBufSize]; // buffer array 8

byte cabecera_wav[44]; // RBP 30/10/2023. Cabecera de los ficheros WAV
byte byte1, byte2, byte3, byte4;
byte byteLeido;
byte byteSonado=1;
byte lastBuf = 7;
byte valOCR2A = 16000000/(8*sampleRate);

bool v_borra_fich;

signed int v_adc, v_adc_ant=0;
byte v_adc_buf;
unsigned long v_time_act;  // RBP 30/10/2023. Marca de tiempo.
unsigned long v_time_borra; // RBP 06/11/2023. Para contar que el pulsador de borrado esté presionado 1 seg.
bool fich_abierto; //  preparado, leelo;

// #define NIVEL_SONIDO 0x2A
#define NIVEL_SONIDO 0x3A

void setup() { // THIS RUNS ONCE
  Setup_ADC();
  pinMode(ledEscucha, OUTPUT);  
  pinMode(ledGraba, OUTPUT);  
  pinMode(altavoz, OUTPUT);
  pinMode(pinMute, OUTPUT);
  pinMode(btnEscucha, INPUT_PULLUP); 
  pinMode(btnBorra, INPUT_PULLUP); 
  pinMode(btnPlay, INPUT_PULLUP); 

  digitalWrite(pinMute, HIGH);
  digitalWrite(ledEscucha, LOW);
  digitalWrite(ledGraba, LOW);
  
  precalc_cabecera_wav(); // RBP 30/10/2023. Precálculo de la cabecera de los ficheros WAV

  memset(buf00, 0xFF, darBufSize);
  memset(buf01, 0xFF, darBufSize);
  memset(buf02, 0xFF, darBufSize);
  memset(buf03, 0xFF, darBufSize);
  memset(buf04, 0xFF, darBufSize);
  memset(buf05, 0xFF, darBufSize);
  memset(buf06, 0xFF, darBufSize);
  memset(buf07, 0xFF, darBufSize);              

  Serial.begin(9600);

  tmrpcm.speakerPin = 46;
  if (SD.begin(chipSelect)) { // }, SPI_FULL_SPEED) { // initialise card on SPI to 8MHz SPI bus speed  
    Serial.println("SD correcta");
    for (int dloop = 0; dloop < 4; dloop++) {
      digitalWrite(ledEscucha,(dloop+1)%2); 
      delay(100);
    }
  } else { // if error, flash LED twice per second, until reset
    Serial.println("fallo SD");
    int v_led =0;
    while(1) {
      v_led =!v_led;
      digitalWrite(ledEscucha,v_led); 
      delay(500);
    }
  }

  Wire.begin();
  
  val_btnEscucha_ant =digitalRead(btnEscucha);
}

void loop() { 

  // gestión del modo "escucha"
  val_btnEscucha =digitalRead(btnEscucha);
  if (val_btnEscucha == LOW && val_btnEscucha_ant == HIGH) { 
    delay(1500);
    Serial.println("escuchando ...");
    StartEscucha(); 
  }
  if (val_btnEscucha == HIGH && val_btnEscucha_ant == LOW) { 
    if (modoGraba) {
      StopGraba();
    }
    StopEscucha(); 
    Serial.println("fin escucha");    
    delay(1500);    
  }   
  val_btnEscucha_ant =val_btnEscucha;

  // gestión del botón de borrado
  val_btnBorra =digitalRead(btnBorra); // RBP 08/11/2023. Lectura del botón de borrado de los audios.
  if (!modoGraba) { // && !tmrpcm.isPlaying()) {    
    if (val_btnBorra == LOW) {
      if (val_btnBorra_ant == HIGH) {  // se toma el tiempo nada más pulsarse
        v_time_borra =millis();
      } else {
        if (v_time_borra!=0 && (millis()-v_time_borra)>1500) { // RBP 06/11/2023. Cuando se mantiene pulsado el botón de borrado por más de 1.5 seg
                                                              // se borran los archivos .WAV de las tarjeta SD.
          for (int dloop = 0; dloop < 4; dloop++) {   // indicación de borrado en el led rojo
            digitalWrite(ledGraba,(dloop+1)%2); 
            delay(100);
          }
          v_time_borra =0;

          borraWavs(); // RBP 07/11/2023. Borra los ficheros WAV.
        }
      }
    } else {
      if (val_btnBorra_ant == LOW) {
        v_time_borra =0;
      }
    }    
  }
  val_btnBorra_ant =val_btnBorra;

  // gestión del botón de play
  val_btnPlay =digitalRead(btnPlay); // RBP 08/11/2023. Lectura del botón de play.
  if (val_btnPlay == LOW) {              
    if (val_btnPlay_ant == HIGH) {   // Nada más pulsarse. 
      // digitalWrite(ledGraba,HIGH); 
      playWavs();
      delay(100);
    }
  }
  val_btnPlay_ant =val_btnPlay;

  // gestión de la grabación
  if (modoGraba) {    
    if (!fich_abierto) {
      escribeCabeceraAudio();                
    }
    fich_abierto =true;

    if (recByteCount % totalBuf > bufSplit_1 && lastBuf == 7) { 
      rec.write(buf00,darBufSize); lastBuf = 0; recByteSaved += darBufSize; 
    } else if (recByteCount % totalBuf > bufSplit_2 && lastBuf == 0) {
      rec.write(buf01,darBufSize); lastBuf = 1; recByteSaved += darBufSize; 
    } else if (recByteCount % totalBuf > bufSplit_3 && lastBuf == 1) { 
      rec.write(buf02,darBufSize); lastBuf = 2; recByteSaved += darBufSize; 
    } else if (recByteCount % totalBuf > bufSplit_4 && lastBuf == 2) { 
      rec.write(buf03,darBufSize); lastBuf = 3; recByteSaved += darBufSize; 
    } else if (recByteCount % totalBuf > bufSplit_5 && lastBuf == 3) { 
      rec.write(buf04,darBufSize); lastBuf = 4; recByteSaved += darBufSize;       
    } else if (recByteCount % totalBuf > bufSplit_6 && lastBuf == 4) { 
      rec.write(buf05,darBufSize); lastBuf = 5; recByteSaved += darBufSize; 
    } else if (recByteCount % totalBuf > bufSplit_7 && lastBuf == 5) { 
      rec.write(buf06,darBufSize); lastBuf = 6; recByteSaved += darBufSize; 
    } else if (recByteCount % totalBuf < bufSplit_1 && lastBuf == 6) { 
      rec.write(buf07,darBufSize); lastBuf = 7; recByteSaved += darBufSize; 
    }             
  } else {
    if (fich_abierto) {        
      terminaAudio();
      fich_abierto =false;
      digitalWrite(ledGraba,LOW); 
    } 
  }
  modoGraba_ant =modoGraba;
}

// inicia modo escucha
void StartEscucha() {
  modoEscucha = true;
  Setup_timer2_rec();

  sbi (TIMSK2, OCIE2A); // enable timer interrupt, start grabbing audio
  digitalWrite(ledEscucha,HIGH);
}

// inicio del proceso de grabación para un nuevo audio
void StartGraba() { 
  digitalWrite(ledGraba,HIGH);
  recByteCount = 0;
  recByteSaved = 0;
  ultSonido = 0;
  lastBuf =7;
  fich_abierto =false;
  modoGraba = true; 
}

// reproduce un audio
void playAudio(char *p_nombrefich) { // RBP. 24/10/2023
  StopGraba();
  StopEscucha();
  tmrpcm.setVolume(4);  // 4
  Serial.println("sonando "+String(p_nombrefich));
  tmrpcm.play(p_nombrefich);
  while (tmrpcm.isPlaying());
  Serial.println("fin audio");
}

// parar proceso de grabación
void StopGraba() { 
  modoGraba = false; 
}

// fin del proceso de escucha
void StopEscucha() { 
  modoGraba = false;
  modoEscucha = false;
  cbi (TIMSK2, OCIE2A); // deshabilitar interrupción del timer 2
  digitalWrite(ledGraba,LOW); 
  digitalWrite(ledEscucha,LOW); 
}

// actualiza la cabecera WAV del audio con el tamaño final del audio
void terminaAudio() { 
  unsigned long j;
  bool hay_voz;
  signed int valor_adc;

  rec.seek(4);
  byte1 = recByteSaved & 0xff;
  byte2 = (recByteSaved >> 8) & 0xff;
  byte3 = (recByteSaved >> 16) & 0xff;
  byte4 = (recByteSaved >> 24) & 0xff;  
  rec.write(byte1);  rec.write(byte2);  rec.write(byte3);  rec.write(byte4);
  rec.seek(40);
  rec.write(byte1);  rec.write(byte2);  rec.write(byte3);  rec.write(byte4);

  rec.seek(ultSonido+44+9600); 
  while (rec.available()) {
    rec.write(0x80); // silencio
  }
  rec.close();
}

// RBP 03/11/2023. Precalcula, en un array, la cabecera del audio para que sea más rápida la creación del mismo.
void precalc_cabecera_wav() { 
  cabecera_wav[0] ='R';
  cabecera_wav[1] ='I';
  cabecera_wav[2] ='F';
  cabecera_wav[3] ='F';
  cabecera_wav[4] =0;
  cabecera_wav[5] =0;
  cabecera_wav[6] =0;
  cabecera_wav[7] =0;
  cabecera_wav[8] ='W';
  cabecera_wav[9] ='A';
  cabecera_wav[10] ='V';
  cabecera_wav[11] ='E';
  cabecera_wav[12] ='f';
  cabecera_wav[13] ='m';
  cabecera_wav[14] ='t';
  cabecera_wav[15] =' ';
  byte1 = waveChunk & 0xff;
  byte2 = (waveChunk >> 8) & 0xff;
  byte3 = (waveChunk >> 16) & 0xff;
  byte4 = (waveChunk >> 24) & 0xff;  
  cabecera_wav[16] =byte1;
  cabecera_wav[17] =byte2;
  cabecera_wav[18] =byte3;
  cabecera_wav[19] =byte4;
  byte1 = waveType & 0xff;
  byte2 = (waveType >> 8) & 0xff;
  cabecera_wav[20] =byte1;
  cabecera_wav[21] =byte2;
  byte1 = numChannels & 0xff;
  byte2 = (numChannels >> 8) & 0xff;
  cabecera_wav[22] =byte1;
  cabecera_wav[23] =byte2;
  byte1 = sampleRate & 0xff;
  byte2 = (sampleRate >> 8) & 0xff;
  byte3 = (sampleRate >> 16) & 0xff;
  byte4 = (sampleRate >> 24) & 0xff;  
  cabecera_wav[24] =byte1;
  cabecera_wav[25] =byte2;
  cabecera_wav[26] =byte3;
  cabecera_wav[27] =byte4;
  byte1 = bytesPerSec & 0xff;
  byte2 = (bytesPerSec >> 8) & 0xff;
  byte3 = (bytesPerSec >> 16) & 0xff;
  byte4 = (bytesPerSec >> 24) & 0xff;  
  cabecera_wav[28] =byte1;
  cabecera_wav[29] =byte2;
  cabecera_wav[30] =byte3;
  cabecera_wav[31] =byte4;
  byte1 = blockAlign & 0xff;
  byte2 = (blockAlign >> 8) & 0xff;
  cabecera_wav[32] =byte1;
  cabecera_wav[33] =byte2;
  byte1 = bitsPerSample & 0xff;
  byte2 = (bitsPerSample >> 8) & 0xff;
  cabecera_wav[34] =byte1;
  cabecera_wav[35] =byte2;
  cabecera_wav[36] ='d';
  cabecera_wav[37] ='a';
  cabecera_wav[38] ='t';
  cabecera_wav[39] ='a';    
  cabecera_wav[40] =0;
  cabecera_wav[41] =0;
  cabecera_wav[42] =0;
  cabecera_wav[43] =0;      
}

// escribe la cabecera WAV del fichero de audio, dándole un nombre basado en la hora
void escribeCabeceraAudio() { 
  recByteSaved = 0;
                                                // librería DS3231:
  nombre_fichero_hora_RTC(nombrefich_graba);    //  obtiene el nombre del fichero en el formato "HH-MI-SS.wav", 
                                                // a partir de la hora del Real Time Clock
  cli(); // deshabilitar interrupciones                                                
  rec =SD.open(nombrefich_graba, O_CREAT | O_TRUNC | O_RDWR);
  rec.write(cabecera_wav,44);  // escrite la cabecera precalculada
  sei(); // habilitar interrupciones
}

// inicia el TIMER2 con la frecuencia de muestreo
void Setup_timer2_rec() {
  TCCR2B = _BV(CS21);  // prescala de Timer2 a : 8
  TCCR2A = _BV(WGM21); // frecuencia de interrupción
  OCR2A = valOCR2A; // ajuste de la frecuencia de muestreo
}

// configura el ADC para leer del micrófono en el pin A6
void Setup_ADC() {
  ADMUX = 0x66; // configura ADC para leer del pin A6, ADLAR a 1 (justificado a la izquierda)
  cbi(ADCSRA,ADPS2); // configura prescala a 3 / ADC clock = 4MHz
  sbi(ADCSRA,ADPS1);
  sbi(ADCSRA,ADPS0);
}

// RBP 07/11/2023. Reproducir todos los audios .WAV del directorio raíz de la tarjeta SD.
void playWavs() { 
  int mylen;
  File dir;

  dir =SD.open("/", O_RDWR);  
  while(true) {
    File entry =  dir.openNextFile();
    
    if (!entry) {
      break;
    }
     
    mylen=strlen(entry.name())-4; // compara los últimos 4 caracteres
     
    entry.close();     
    if ((strcmp(entry.name() + mylen,".WAV")==0) || 
        (strcmp(entry.name() + mylen,".wav")==0)) {
      digitalWrite(pinMute, LOW);          
      playAudio(entry.name());
    } 
  }
  dir.close();
  digitalWrite(pinMute, HIGH);
}

// RBP 07/11/2023. Borrar todos los audios .WAV del directorio raíz de la tarjeta SD.
void borraWavs() { 
  int mylen;
  File dir;

  dir =SD.open("/", O_RDWR);  
  while(true) {
    File entry =  dir.openNextFile();
    
    if (!entry) {
      break;
    }
     
    mylen=strlen(entry.name())-4; // compara los últimos 4 caracteres
     
    entry.close();     
    if ((strcmp(entry.name() + mylen,".WAV")==0) || 
        (strcmp(entry.name() + mylen,".wav")==0)) {
      SD.remove(entry.name()); // RBP 07/11/2023. Borrar el fichero WAV
    } 
  }
  dir.close();
}

// ISR, rutina de interrupción de servicio del TIMER2
ISR(TIMER2_COMPA_vect) {
  cli(); // deshabilitar interrupciones  

  sbi(ADCSRA, ADSC); // inicio muestreo ADC 
  while(bit_is_set(ADCSRA, ADSC));  // esperar hasta que el bit ADSC pase a 0 = nueva muestra lista
  recByteCount++; // incrementar contador de muestras
  
  v_adc =ADCH-0x40; // quitar el offset de 1.25V que trae el módulo del micrófono
  if (abs(v_adc) >NIVEL_SONIDO) { // RBP 31/10/2023. Si hay un nivel de sonido.
    if (!modoGraba) {  
      StartGraba(); // RBP 30/10/2023. Comienzo de la grabación.    
    } 
    v_time_act =millis(); // RBP 31/10/2023. Actualizamos la referencia de tiempo.
    ultSonido =recByteCount;
  } 
  if (modoGraba && (millis()-v_time_act)>2000) { // RBP 31/10/2023. Si pasan más de 2 segundos sin sonido.
    StopGraba(); // RBP 30/10/2023. Fin de la grabación.    
  }

  if (modoGraba) {
    v_adc =(v_adc + v_adc_ant)>>1;   // filtro para suavizar ruido 
    v_adc_buf =(v_adc+0x80) & 0xFF;  // volver a poner un offset de 2.5V para que el valor esté comprendido entre 0 y 255, sin signo

    if (recByteCount % totalBuf < bufSplit_1) { // guardamos en los bufferes el valor final de la muestra, para que el bucle principal
                                                // lo escriba en el fichero, por bloques 
      buf00[recByteCount % darBufSize] = v_adc_buf;
    } else if (recByteCount % totalBuf < bufSplit_2) { 
      buf01[recByteCount % darBufSize] = v_adc_buf;
    } else if (recByteCount % totalBuf < bufSplit_3) {
      buf02[recByteCount % darBufSize] = v_adc_buf;
    } else if (recByteCount % totalBuf < bufSplit_4) {
      buf03[recByteCount % darBufSize] = v_adc_buf;
    } else if (recByteCount % totalBuf < bufSplit_5) {
      buf04[recByteCount % darBufSize] = v_adc_buf;
    } else if (recByteCount % totalBuf < bufSplit_6) {
      buf05[recByteCount % darBufSize] = v_adc_buf;
    } else if (recByteCount % totalBuf < bufSplit_7) {
      buf06[recByteCount % darBufSize] = v_adc_buf;
    } else {
      buf07[recByteCount % darBufSize] = v_adc_buf;
    }
  }
  v_adc_ant =v_adc;  // valor anterior para filtro de ruido  
  v_adc =0;

  sei(); // habilitar interrupciones
}

Código del archivo DS3231.ino
#include <Wire.h> // El reloj en tiempo real va por el bus I2C
#include <DS3231.h>

//Variables RTC
byte Year ;
byte Month ;
byte Date ;
byte DoW ;
byte Hour ;
byte Minute ;
byte Second ;
bool Century 	= false;
bool h12 ;
bool PM ;

// RTC
DS3231 Clock;

// RBP. 29/10/2023. Para obtener el nombre del fichero en formato "HH-MI-SS.wav"
void nombre_fichero_hora_RTC(char *nombre_fichero) {
 sprintf(nombre_fichero, "%02d-%02d-%02d.wav\0", Clock.getHour(h12, PM), Clock.getMinute(), Clock.getSecond());
}

// Funciones del RTC -----------------------------------------------------------------------------
void readRTC( ) { // function readRTC 
 	////Read Real Time Clock
 	Serial.print(Clock.getYear(), DEC);
 	Serial.print("-");
 	Serial.print(Clock.getMonth(Century), DEC);
 	Serial.print("-");
 	Serial.print(Clock.getDate(), DEC);
 	Serial.print(" ");
 	Serial.print(Clock.getHour(h12, PM), DEC); //24-hr
 	Serial.print(":");
 	Serial.print(Clock.getMinute(), DEC);
 	Serial.print(":");
 	Serial.println(Clock.getSecond(), DEC);
 	delay(1000);
}

void setDate( ) { // function setDate 
 	////Set Real Time Clock
 	if (Serial.available()) {

 			//int _start = millis();

 			GetDateStuff(Year, Month, Date, DoW, Hour, Minute, Second);

 			Clock.setClockMode(false); 	// set to 24h

 			Clock.setSecond(Second);
 			Clock.setMinute(Minute);
 			Clock.setHour(Hour);
 			Clock.setDate(Date);
 			Clock.setMonth(Month);
 			Clock.setYear(Year);
 			Clock.setDoW(DoW);

 	}
}

void GetDateStuff(byte& Year, byte& Month, byte& Day, byte& DoW, byte& Hour, byte& Minute, byte& Second) { // function GetDateStuff 
 	////Get date data
 	// Call this if you notice something coming in on
 	// the serial port. The stuff coming in should be in
 	// the order YYMMDDwHHMMSS, with an 'x' at the end.
 	boolean GotString = false;
 	char InChar;
 	byte Temp1, Temp2;
 	char InString[20];

 	byte j = 0;
 	while (!GotString) {
 			if (Serial.available()) {
 					InChar = Serial.read();
 					InString[j] = InChar;
 					j += 1;
 					if (InChar == 'x') {
 							GotString = true;
 					}
 			}
 	}
 	Serial.println(InString);
 	// Read Year first
 	Temp1 = (byte)InString[0] - 48;
 	Temp2 = (byte)InString[1] - 48;
 	Year = Temp1 * 10 + Temp2;
 	// now month
 	Temp1 = (byte)InString[2] - 48;
 	Temp2 = (byte)InString[3] - 48;
 	Month = Temp1 * 10 + Temp2;
 	// now date
 	Temp1 = (byte)InString[4] - 48;
 	Temp2 = (byte)InString[5] - 48;
 	Day = Temp1 * 10 + Temp2;
 	// now Day of Week
 	DoW = (byte)InString[6] - 48;
 	// now Hour
 	Temp1 = (byte)InString[7] - 48;
 	Temp2 = (byte)InString[8] - 48;
 	Hour = Temp1 * 10 + Temp2;
 	// now Minute
 	Temp1 = (byte)InString[9] - 48;
 	Temp2 = (byte)InString[10] - 48;
 	Minute = Temp1 * 10 + Temp2;
 	// now Second
 	Temp1 = (byte)InString[11] - 48;
 	Temp2 = (byte)InString[12] - 48;
 	Second = Temp1 * 10 + Temp2;
}

Existen seis estados en el proyecto:

Sin tarjeta SD: al encender el sistema y no encontrar la tarjeta el LED amarillo, el de escucha, empezará a parpadear continuamente indicando este hecho.

Standby: al encender el sistema y este tener tarjeta SD el LED amarillo parpadea y después se apaga indicando que todo está correcto, en este modo se puede borrar los archivos de la tarjeta SD, reproducir los archivos WAV de la tarjeta, o entrar en el modo escucha.

Borrado: al estar en Standby si se mantiene presionado el botón rojo(Borrar) por un segundo y medio, este borrará los archivos con extensión WAV de la tarjeta y volverá al modo anterior(Standby).

Reproducción: si se presiona el botón negro(Play) estando en modo Standby, el sistema reproducirá todos los archivos .WAV de la tarjeta SD, tras finalizar, al igual que en el modo de Borrado, volverá a Standby.

Escucha: accionando el interruptor Escucha, el dispositivo encenderá el led amarillo y este se mantendrá encendido hasta que se abandone el modo Escucha y el modo Grabación. Al detectar sonido más alto que el umbral definido en el código, entrará en modo Grabación. Se volverá al modo Standby al volver a cambiar la posición del interruptor.

Grabación: como se ha dicho antes si se está en modo escucha y se supera el umbral de volumen entrará en este modo, al hacerlo comenzará a grabar el sonido y el LED rojo(Grabando) se encenderá. Tras no escuchar sonido que supere el umbral durante un breve lapso de tiempo el LED se apagará y se creará el archivo WAV en la tarjeta SD con nombre de la hora minutos y segundos a la que ha sido grabado. Tras esto regresará al modo Escucha.

Algunos problemas más leves que fuimos encontrado a medida que se realizaba el proyecto fueron los siguientes: falta de memoria, falta de conocimiento de los componentes utilizados/código necesario. El primer problema se solucionó empleando una Arduino MEGA, y el segundo buscando información a cerca de la grabación/reproducción, el RTC y el almacenamiento en la SD.

Otro problema más importante relacionado con los objetivos del proyecto fue la dificultad al reconocer la apnea, este objetivo finalmente se eliminó y se pasó a grabar el sonido y grabarlo en la SD y el resultado de estas sería analizado por un profesional.

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *