Documentación Técnica

Fecha: 14 de noviembre de 2025

Este documento contiene la documentación técnica para el proyecto STEAM del curso Laboratorio STEAM+ de la tecnicatura Redes y Software del Instituto Tecnológico de Informática de UTU año 2025.

Proyecto: Bandmate

1.Integrantes

  • Hernán Sierra
  • Raúl Vidal
  • Ezequiel Valiunas
  • Tabaré Maciel

2. Descripción

El Bandmate Project surge como una iniciativa interdisciplinaria que combina electrónica, programación y teoría musical con un objetivo claro: diseñar un dispositivo inteligente de afinación automática para instrumentos musicales. El proyecto fue concebido por un equipo integrado por Ezequiel Valiunas, Hernán Darío Sierra, Tabaré Maciel y Raúl Vidal, quienes aportan su conocimiento técnico y su pasión por la música al desarrollo de una herramienta innovadora.

3. Materiales

4 . Diseño Mecánico

Para lo que se refiere al diseño mecánico precisaremos una guitarra , tocar la nota seleccionada a través de la botonera , para su mejor funcionamiento se recomienda no estar a mas de 30 cmm del módulo MAX9814 para su correcto funcionamiento. Aunque la mejor manera seria insertarlo dentro de la caja de resonancia de la guitarra para que no se obstruya con el sonido ambiente (aunque es susceptible al estar demasiado cerca de la fuente de sonido)

5. Diseño Electrónico

En lo que respecta al Diseño Electrónico utilizaremos un protoboard donde se interconectaran los diferentes módulos .

Luego se conectan tanto el micrófono MAX9814 y el módulo display el I2C al Arduino

Conexión de los pines tanto al arduino como al protoboard

El módulo display el I2C al arduino va conectado de la siguiente manera

LCD I2CArduino UNO
VCC5V Alimentación
GNDGND Tierra común
SDAA4 Datos I2C
SLCA5 Reloj I2C

Para el Micrófono debe quedar de la siguiente manera

Módulo MAX9814Arduino UNO
OUT A0 (entrada analógica)
VCC5V
GNDGND

Los Botones quedan conectados de la siguiente manera

Botón Arduino UNO
6° Cuerda PIN DIGITAL 1
5° Cuerda PIN DIGITAL 2
4° Cuerda PIN DIGITAL 3
3° Cuerda PIN DIGITAL 4
2° Cuerda PIN DIGITAL 5
1° Cuerda PIN DIGITAL 6

En este caso del diagrama en Tinkercad en vez del sensor de movimiento infrarrojo seria el micrófono MAX 9814

6. Software de Diseño

Medición de Frecuencia

para medir la frecuencia con el módulo MAX9814 se activa con mayor intensidad dependiendo de los hz que emite cada cuerda al ser golpeada generando así el sonido. Calculando con FFT, que es un algoritmo eficiente que calcula la Transformada Discreta de Fourier

A continuación presentaremos el código para el Arduino UNO para la programación del prototipo.

Con el fin de tener un código mas limpio y organizado , decidimos tener el programa separado por funciones. Dividiéndose así en 8 capas :

Bandmate.ino

#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include "config.h"
#include "audio.h"
#include "display.h"

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Pines de botones
#define BUTTON_E6 2
#define BUTTON_A5 3
#define BUTTON_D4 4
#define BUTTON_G3 5
#define BUTTON_B2 6
#define BUTTON_E1 7

int selectedString = -1;

void setup() {
  Serial.begin(9600);

  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("BandMate listo!");
  delay(1000);

  audioSetup();

  pinMode(BUTTON_E6, INPUT_PULLUP);
  pinMode(BUTTON_A5, INPUT_PULLUP);
  pinMode(BUTTON_D4, INPUT_PULLUP);
  pinMode(BUTTON_G3, INPUT_PULLUP);
  pinMode(BUTTON_B2, INPUT_PULLUP);
  pinMode(BUTTON_E1, INPUT_PULLUP);
}

void loop() {
  checkButtons();
  processAudio();
  displayUpdate(selectedString);
  delay(50);
}

// Función para leer botones y actualizar cuerda seleccionada
void checkButtons() {
  int previous = selectedString;

  if (digitalRead(BUTTON_E6) == LOW) selectedString = 0;
  else if (digitalRead(BUTTON_A5) == LOW) selectedString = 1;
  else if (digitalRead(BUTTON_D4) == LOW) selectedString = 2;
  else if (digitalRead(BUTTON_G3) == LOW) selectedString = 3;
  else if (digitalRead(BUTTON_B2) == LOW) selectedString = 4;
  else if (digitalRead(BUTTON_E1) == LOW) selectedString = 5;
  else selectedString = -1;

  // Mostrar mensaje temporal si cambió la selección
  if (selectedString != previous && selectedString != -1) {
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Afinar ");
    lcd.print(getStringInfoByIndex(selectedString));
    delay(1000); // 1 segundo de feedback
  }
}

// Función auxiliar para convertir índice a texto
const char* getStringInfoByIndex(int index) {
  switch(index) {
    case 0: return "6ta cuerda";
    case 1: return "5ta cuerda";
    case 2: return "4ta cuerda";
    case 3: return "3ra cuerda";
    case 4: return "2da cuerda";
    case 5: return "1ra cuerda";
    default: return "Desconocida";
  }
}

audio.cpp

#include "audio.h"
#include <arduinoFFT.h>
#include <LiquidCrystal_I2C.h>

extern LiquidCrystal_I2C lcd;

double vReal[SAMPLES];
double vImag[SAMPLES];

ArduinoFFT<double> FFT(vReal, vImag, SAMPLES, SAMPLING_FREQUENCY);

void audioInit() {
  pinMode(MIC_PIN, INPUT);
  Serial.println("Micrófono inicializado en pin A0");
}

void audioSetup() {
  audioInit();
  Serial.println("Sistema de audio listo");
}

void processAudio() {
  captureFrequency(); // Solo captura frecuencia
}

double captureFrequency() {
  for (int i = 0; i < SAMPLES; i++) {
    vReal[i] = analogRead(MIC_PIN) - 512; // Centrar señal
    vImag[i] = 0;
  }

  FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward);
  FFT.compute(FFTDirection::Forward);
  FFT.complexToMagnitude();

  double peak = 0;
  int index = 0;
  for (int i = 1; i < SAMPLES / 2; i++) {
    if (vReal[i] > peak) {
      peak = vReal[i];
      index = i;
    }
  }

  double freq = (index * ((double)SAMPLING_FREQUENCY / SAMPLES));

  if (peak < 5 || freq < 60 || freq > 400) return 0;

  return freq;
}

void testMicrophone() {
  int reading = analogRead(MIC_PIN);
  Serial.print("Current ADC: ");
  Serial.print(reading);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("ADC Test:");
  lcd.setCursor(0, 1);
  lcd.print(reading);

  if (reading > 100) Serial.println(" - Signal detected!");
  else Serial.println(" - Low signal");
}

double getAverageFrequency() {
  // Promediar varias lecturas para estabilidad
  double sum = 0;
  int count = 5;
  for(int i = 0; i < count; i++) sum += captureFrequency();
  return sum / count;
}

audio.h

#ifndef AUDIO_H
#define AUDIO_H

#include <arduinoFFT.h>
#include "config.h"

extern double vReal[SAMPLES];
extern double vImag[SAMPLES];

void audioSetup();
void processAudio();
double captureFrequency();
void testMicrophone();
double getAverageFrequency();

#endif

config.h

#ifndef CONFIG_H
#define CONFIG_H

#include <Arduino.h>

// Pines y configuración general
#define MIC_PIN A0
#define SAMPLES 128
#define SAMPLING_FREQUENCY 6000  // Hz

#endif

display.cpp

#include "display.h"
#include "audio.h"
#include "notes.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

extern LiquidCrystal_I2C lcd;

void displayNoteInfo(const char* note, double diff, double freq) {
  lcd.clear();
  lcd.setCursor(0, 0);

  const char* stringInfo = getStringInfo(note);
  lcd.print(stringInfo);
  lcd.print("-");
  char noteName[4];
  strncpy(noteName, note, 2);
  noteName[2] = '\0';
  lcd.print(noteName);

  lcd.setCursor(0, 1);
  if (diff > 15) lcd.print(">> MUY AGUDO");
  else if (diff < -15) lcd.print(">> MUY GRAVE");
  else if (diff > 5) lcd.print("> AGUDO");
  else if (diff < -5) lcd.print("> GRAVE");
  else lcd.print("** AFINADO **");
}

void displayWaiting() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Esperando nota...");
}

const char* getStringInfo(const char* note) {
  if (strstr(note, "E2")) return "6ta cuerda";
  if (strstr(note, "A2")) return "5ta cuerda";
  if (strstr(note, "D3")) return "4ta cuerda";
  if (strstr(note, "G3")) return "3ra cuerda";
  if (strstr(note, "B3")) return "2da cuerda";
  if (strstr(note, "E4")) return "1ra cuerda";
  return "Desconocida";
}

int getStringIndex(const char* note) {
  if (strstr(note, "E2")) return 0;
  if (strstr(note, "A2")) return 1;
  if (strstr(note, "D3")) return 2;
  if (strstr(note, "G3")) return 3;
  if (strstr(note, "B3")) return 4;
  if (strstr(note, "E4")) return 5;
  return -1;
}

void displayUpdate(int selectedString) {
  double frequency = getAverageFrequency();

  if (frequency > 0) {
    NoteRef note = nearestNote(frequency);
    double diff = centsDiff(frequency, note.freq);

    if (selectedString != -1 && getStringIndex(note.name) != selectedString) {
      displayWaiting();
      return;
    }

    displayNoteInfo(note.name, diff, frequency);
  } else {
    displayWaiting();
  }
}

display.h

#ifndef DISPLAY_H
#define DISPLAY_H

#include <LiquidCrystal_I2C.h>

extern LiquidCrystal_I2C lcd;

void displayUpdate(int selectedString);
void displayNoteInfo(const char* note, double diff, double freq);
void displayWaiting();
const char* getStringInfo(const char* note);
int getStringIndex(const char* note);

#endif

notes.cpp

#include "notes.h"
#include <math.h>

NoteRef notes[] = {
  {"E2-6ta", 82.41}, {"A2-5ta", 110.00}, {"D3-4ta", 146.83},
  {"G3-3ra", 196.00}, {"B3-2da", 246.94}, {"E4-1ra", 329.63}
};

double centsDiff(double f, double ref) {
  if (f <= 0 || ref <= 0) return 0;
  return 1200.0 * log(f / ref) / log(2.0);
}

NoteRef nearestNote(double f) {
  NoteRef best = notes[0];
  double bestDiff = 1e9;
  for (int i = 0; i < 6; i++) {
    double d = fabs(centsDiff(f, notes[i].freq));
    if (d < bestDiff) { bestDiff = d; best = notes[i]; }
  }
  return best;
}

notes.h

#ifndef NOTES_H
#define NOTES_H

struct NoteRef {
  const char* name;
  double freq;
};

extern NoteRef notes[6];

double centsDiff(double f, double ref);
NoteRef nearestNote(double f);

#endif

7. Referencias y recursos