/*
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Encoder.h>
// --- Configuración de la Pantalla ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- Configuración de Pines de Encoders ---
// Volumen
#define VOL_CLK_PIN 19
#define VOL_DT_PIN 18
// Ganancia
#define GAIN_CLK_PIN 17
#define GAIN_DT_PIN 16
// Agudos
#define HIGH_CLK_PIN 5
#define HIGH_DT_PIN 4
// Medios-Agudos
#define MID_HIGH_CLK_PIN 2
#define MID_HIGH_DT_PIN 15
// Medios-Graves
#define MID_LOW_CLK_PIN 25
#define MID_LOW_DT_PIN 26
// Graves
#define LOW_CLK_PIN 27
#define LOW_DT_PIN 33
// --- Rangos de Decibelios (dB) ---
const int VOL_MIN = -80, VOL_MAX = 0;
const int GAIN_MIN = -10, GAIN_MAX = 40;
const int EQ_MIN = -15, EQ_MAX = 15; // Rango para HIGH, MIDs y LOW
// --- Objetos Encoder ---
ESP32Encoder encoderVol;
ESP32Encoder encoderGain;
ESP32Encoder encoderHigh;
ESP32Encoder encoderMidHigh;
ESP32Encoder encoderMidLow;
ESP32Encoder encoderLow;
// --- Variables de estado para cada parámetro (en dB) ---
int currentVolDB = 0;
int currentGainDB = 0;
int currentHighDB = 0;
int currentMidHighDB = 0;
int currentMidLowDB = 0;
int currentLowDB = 0;
// <-- CAMBIO: Define cuántos pulsos del encoder corresponden a 1 dB.
// Con attachHalfQuad, normalmente son 2. Si vieras saltos de 2, cambia este valor a 4.
#define ENCODER_STEPS_PER_DB 2
void setup() {
Serial.begin(115200);
// --- Inicialización de la pantalla OLED ---
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("Fallo al iniciar SSD1306"));
for(;;);
}
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(15, 24);
display.println("PATRO DJ");
display.display();
delay(1000);
// Volumen: Inicia en -80 dB (silencio)
encoderVol.attachHalfQuad(VOL_DT_PIN, VOL_CLK_PIN);
encoderVol.setCount(VOL_MIN * ENCODER_STEPS_PER_DB); // <-- CAMBIO: Multiplicamos por los pasos
currentVolDB = VOL_MIN;
// Ganancia: Inicia en 0 dB
encoderGain.attachHalfQuad(GAIN_DT_PIN, GAIN_CLK_PIN);
encoderGain.setCount(0 * ENCODER_STEPS_PER_DB); // <-- CAMBIO: Multiplicamos por los pasos
currentGainDB = 0;
// Agudos (High): Inicia en 0 dB
encoderHigh.attachHalfQuad(HIGH_DT_PIN, HIGH_CLK_PIN);
encoderHigh.setCount(0 * ENCODER_STEPS_PER_DB); // <-- CAMBIO: Multiplicamos por los pasos
currentHighDB = 0;
// Medios-Agudos (Mid High): Inicia en 0 dB
encoderMidHigh.attachHalfQuad(MID_HIGH_DT_PIN, MID_HIGH_CLK_PIN);
encoderMidHigh.setCount(0 * ENCODER_STEPS_PER_DB); // <-- CAMBIO: Multiplicamos por los pasos
currentMidHighDB = 0;
// Medios-Graves (Mid Low): Inicia en 0 dB
encoderMidLow.attachHalfQuad(MID_LOW_DT_PIN, MID_LOW_CLK_PIN);
encoderMidLow.setCount(0 * ENCODER_STEPS_PER_DB); // <-- CAMBIO: Multiplicamos por los pasos
currentMidLowDB = 0;
// Graves (Low): Inicia en 0 dB
encoderLow.attachHalfQuad(LOW_DT_PIN, LOW_CLK_PIN);
encoderLow.setCount(0 * ENCODER_STEPS_PER_DB); // <-- CAMBIO: Multiplicamos por los pasos
currentLowDB = 0;
// Forzar la primera actualización para mostrar el volumen al inicio
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
}
void loop() {
// 1. Encoder de VOLUMEN
// <-- CAMBIO: Leemos el contador y lo dividimos para obtener pasos de 1 dB
long newVol = encoderVol.getCount() / ENCODER_STEPS_PER_DB;
if (newVol > VOL_MAX) {
newVol = VOL_MAX;
encoderVol.setCount(newVol * ENCODER_STEPS_PER_DB); // Resincronizar encoder
}
if (newVol < VOL_MIN) {
newVol = VOL_MIN;
encoderVol.setCount(newVol * ENCODER_STEPS_PER_DB); // Resincronizar encoder
}
if (newVol != currentVolDB) {
currentVolDB = newVol;
Serial.printf("VOLUMEN: %d dB\n", currentVolDB);
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
}
// 2. Encoder de GAIN
long newGain = encoderGain.getCount() / ENCODER_STEPS_PER_DB;
if (newGain > GAIN_MAX) {
newGain = GAIN_MAX;
encoderGain.setCount(newGain * ENCODER_STEPS_PER_DB);
}
if (newGain < GAIN_MIN) {
newGain = GAIN_MIN;
encoderGain.setCount(newGain * ENCODER_STEPS_PER_DB);
}
if (newGain != currentGainDB) {
currentGainDB = newGain;
Serial.printf("GAIN: %d dB\n", currentGainDB);
actualizarPantalla("GAIN", currentGainDB, GAIN_MIN, GAIN_MAX);
}
// 3. Encoder de HIGH
long newHigh = encoderHigh.getCount() / ENCODER_STEPS_PER_DB;
if (newHigh > EQ_MAX) {
newHigh = EQ_MAX;
encoderHigh.setCount(newHigh * ENCODER_STEPS_PER_DB);
}
if (newHigh < EQ_MIN) {
newHigh = EQ_MIN;
encoderHigh.setCount(newHigh * ENCODER_STEPS_PER_DB);
}
if (newHigh != currentHighDB) {
currentHighDB = newHigh;
Serial.printf("HIGH: %d dB\n", currentHighDB);
actualizarPantalla("HIGH", currentHighDB, EQ_MIN, EQ_MAX);
}
// 4. Encoder de MID HIGH
long newMidHigh = encoderMidHigh.getCount() / ENCODER_STEPS_PER_DB;
if (newMidHigh > EQ_MAX) {
newMidHigh = EQ_MAX;
encoderMidHigh.setCount(newMidHigh * ENCODER_STEPS_PER_DB);
}
if (newMidHigh < EQ_MIN) {
newMidHigh = EQ_MIN;
encoderMidHigh.setCount(newMidHigh * ENCODER_STEPS_PER_DB);
}
if (newMidHigh != currentMidHighDB) {
currentMidHighDB = newMidHigh;
Serial.printf("MID HIGH: %d dB\n", currentMidHighDB);
actualizarPantalla("MID HIGH", currentMidHighDB, EQ_MIN, EQ_MAX);
}
// 5. Encoder de MID LOW
long newMidLow = encoderMidLow.getCount() / ENCODER_STEPS_PER_DB;
if (newMidLow > EQ_MAX) {
newMidLow = EQ_MAX;
encoderMidLow.setCount(newMidLow * ENCODER_STEPS_PER_DB);
}
if (newMidLow < EQ_MIN) {
newMidLow = EQ_MIN;
encoderMidLow.setCount(newMidLow * ENCODER_STEPS_PER_DB);
}
if (newMidLow != currentMidLowDB) {
currentMidLowDB = newMidLow;
Serial.printf("MID LOW: %d dB\n", currentMidLowDB);
actualizarPantalla("MID LOW", currentMidLowDB, EQ_MIN, EQ_MAX);
}
// 6. Encoder de LOW
long newLow = encoderLow.getCount() / ENCODER_STEPS_PER_DB;
if (newLow > EQ_MAX) {
newLow = EQ_MAX;
encoderLow.setCount(newLow * ENCODER_STEPS_PER_DB);
}
if (newLow < EQ_MIN) {
newLow = EQ_MIN;
encoderLow.setCount(newLow * ENCODER_STEPS_PER_DB);
}
if (newLow != currentLowDB) {
currentLowDB = newLow;
Serial.printf("LOW: %d dB\n", currentLowDB);
actualizarPantalla("LOW", currentLowDB, EQ_MIN, EQ_MAX);
}
delay(20); // Pequeña pausa para no saturar el bucle
}
void actualizarPantalla(const char *paramName, int currentValue, int minValue, int maxValue) {
display.clearDisplay();
// 1. Dibuja el nombre y el valor numérico
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(paramName, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 2);
display.println(paramName);
char dbStr[10];
sprintf(dbStr, "%+d dB", currentValue);
display.getTextBounds(dbStr, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 20);
display.print(dbStr);
// 2. Dibuja la barra de progreso
int barWidth = map(currentValue, minValue, maxValue, 0, SCREEN_WIDTH);
display.drawRect(0, 42, SCREEN_WIDTH, 16, SSD1306_WHITE);
if (minValue < 0 && maxValue > 0) {
int zeroPoint = map(0, minValue, maxValue, 0, SCREEN_WIDTH);
display.drawFastVLine(zeroPoint, 40, 20, SSD1306_WHITE);
if (barWidth > zeroPoint) {
display.fillRect(zeroPoint, 42, barWidth - zeroPoint, 16, SSD1306_WHITE);
} else {
display.fillRect(barWidth, 42, zeroPoint - barWidth, 16, SSD1306_WHITE);
}
} else {
display.fillRect(0, 42, barWidth, 16, SSD1306_WHITE);
}
// 3. Muestra todo en la pantalla
display.display();
}
*/
/*
Pines CS: Asegúrate de que los pines que definiste para ..._CS_PIN estén libres y conéctalos correctamente. He usado pines GPIO comunes como ejemplo.
Mapeo Invertido: Fíjate en el comentario dentro de la función setPotValue. Es crucial que map(..., dbMin, dbMax, 255, 0) tenga los valores de
salida (255, 0) invertidos. Esto asegura que cuando el volumen está al mínimo (-80 dB), la resistencia del potenciómetro es máxima, y viceversa.
Control Estéreo: Como se menciona en el código, cada MCP4231 tiene dos potenciómetros. El código actual solo usa el primero (Wiper 0). Si necesitas
controlar una señal estéreo (canales izquierdo y derecho) con un solo encoder, simplemente descomenta la línea writeToPot(csPin, 0x10, (byte)potStep);
en la función setPotValue. Esto enviará el mismo valor al segundo potenciómetro del chip.
Velocidad SPI: He configurado la velocidad a 1 MHz (1000000) en SPISettings, que es una velocidad segura y común para estos chips. Puedes aumentarla
si es necesario, pero 1 MHz es más que suficiente.
Depuración: He añadido una línea Serial.printf en setPotValue para que puedas ver en el Monitor Serie qué valor se está enviando a cada potenciómetro.
Es muy útil para verificar que todo funciona como esperas. Puedes eliminarla cuando el proyecto esté terminado.
Función SPI Pin por Defecto (VSPI) Pin por Defecto (HSPI) Descripción
SCK GPIO 18 GPIO 14 Reloj. Conectar a los pines SCK de todos tus MCP4231.
MOSI GPIO 23 GPIO 13 Datos del ESP32 -> MCP. Conectar a los pines SDI de todos tus MCP4231.
MISO GPIO 19 GPIO 12 Datos del MCP -> ESP32. Conectar a los pines SDO de todos tus MCP4231.
CS / SS GPIO 5 GPIO 15 Selección de Esclavo. No uses este pin. En su lugar, usa cualquier otro
pin GPIO libre para cada uno de tus 6 chips.
//V1
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Encoder.h>
#include <SPI.h>
// --- Configuración de la Pantalla ---
// --- SCL=22, SDA=21
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- Configuración de Pines de Encoders ---
#define VOL_CLK_PIN 12
#define VOL_DT_PIN 13
#define GAIN_CLK_PIN 1 //TX
#define GAIN_DT_PIN 3 //RX
#define HIGH_CLK_PIN 34
#define HIGH_DT_PIN 35
#define MID_HIGH_CLK_PIN 32
#define MID_HIGH_DT_PIN 33
#define MID_LOW_CLK_PIN 25
#define MID_LOW_DT_PIN 26
#define LOW_CLK_PIN 27
#define LOW_DT_PIN 14
// --- Pines Chip Select (CS) para MCP4231/503
// --- Pines SPI: MOSI=23, MISO=19, SCK=18
#define VOL_CS_PIN 15
#define GAIN_CS_PIN 5 //CS/SS
#define HIGH_CS_PIN 17 //TX2
#define MID_HIGH_CS_PIN 16 //RX2
#define MID_LOW_CS_PIN 4
#define LOW_CS_PIN 2
// --- Rangos de Decibelios (dB)
const int VOL_MIN = -80, VOL_MAX = 0;
const int GAIN_MIN = -20, GAIN_MAX = 20;
const int EQ_MIN = -15, EQ_MAX = 15;
// --- Constantes para el Potenciómetro Digital
const int POT_MAX_STEPS = 255; // El MCP4231 tiene 256 pasos (0 a 255)
// --- Objetos Encoder ---
ESP32Encoder encoderVol;
ESP32Encoder encoderGain;
ESP32Encoder encoderHigh;
ESP32Encoder encoderMidHigh;
ESP32Encoder encoderMidLow;
ESP32Encoder encoderLow;
// --- Variables de estado para cada parámetro (en dB)
int currentVolDB = 0;
int currentGainDB = 0;
int currentHighDB = 0;
int currentMidHighDB = 0;
int currentMidLowDB = 0;
int currentLowDB = 0;
#define ENCODER_STEPS_PER_DB 2
// Prototipos de las nuevas funciones
void writeToPot(int csPin, byte address, byte value);
void setPotValue(int csPin, int dbValue, int dbMin, int dbMax);
void setup() {
//Serial.begin(115200);
SPI.begin(); // Iniciar el bus SPI
// --- Inicialización de SPI y pines CS
pinMode(VOL_CS_PIN, OUTPUT);
pinMode(GAIN_CS_PIN, OUTPUT);
pinMode(HIGH_CS_PIN, OUTPUT);
pinMode(MID_HIGH_CS_PIN, OUTPUT);
pinMode(MID_LOW_CS_PIN, OUTPUT);
pinMode(LOW_CS_PIN, OUTPUT);
// Deseleccionar todos los chips al inicio (poner CS en ALTO)
digitalWrite(VOL_CS_PIN, HIGH);
digitalWrite(GAIN_CS_PIN, HIGH);
digitalWrite(HIGH_CS_PIN, HIGH);
digitalWrite(MID_HIGH_CS_PIN, HIGH);
digitalWrite(MID_LOW_CS_PIN, HIGH);
digitalWrite(LOW_CS_PIN, HIGH);
// --- Inicialización de Encoders ---
// Volumen: Inicia en -80 dB (silencio)
encoderVol.attachHalfQuad(VOL_DT_PIN, VOL_CLK_PIN);
encoderVol.setCount(VOL_MIN * ENCODER_STEPS_PER_DB);
currentVolDB = VOL_MIN;
// Ganancia: Inicia en 0 dB
encoderGain.attachHalfQuad(GAIN_DT_PIN, GAIN_CLK_PIN);
encoderGain.setCount(0 * ENCODER_STEPS_PER_DB);
currentGainDB = 0;
// Agudos (High): Inicia en 0 dB
encoderHigh.attachHalfQuad(HIGH_DT_PIN, HIGH_CLK_PIN);
encoderHigh.setCount(0 * ENCODER_STEPS_PER_DB);
currentHighDB = 0;
// Medios-Agudos (Mid High): Inicia en 0 dB
encoderMidHigh.attachHalfQuad(MID_HIGH_DT_PIN, MID_HIGH_CLK_PIN);
encoderMidHigh.setCount(0 * ENCODER_STEPS_PER_DB);
currentMidHighDB = 0;
// Medios-Graves (Mid Low): Inicia en 0 dB
encoderMidLow.attachHalfQuad(MID_LOW_DT_PIN, MID_LOW_CLK_PIN);
encoderMidLow.setCount(0 * ENCODER_STEPS_PER_DB);
currentMidLowDB = 0;
// Graves (Low): Inicia en 0 dB
encoderLow.attachHalfQuad(LOW_DT_PIN, LOW_CLK_PIN);
encoderLow.setCount(0 * ENCODER_STEPS_PER_DB);
currentLowDB = 0;
// --- Configurar el estado inicial de los potenciómetros
//Serial.println("Configurando estado inicial de los potenciómetros...");
setPotValue(VOL_CS_PIN, currentVolDB, VOL_MIN, VOL_MAX);
setPotValue(GAIN_CS_PIN, currentGainDB, GAIN_MIN, GAIN_MAX);
setPotValue(HIGH_CS_PIN, currentHighDB, EQ_MIN, EQ_MAX);
setPotValue(MID_HIGH_CS_PIN, currentMidHighDB, EQ_MIN, EQ_MAX);
setPotValue(MID_LOW_CS_PIN, currentMidLowDB, EQ_MIN, EQ_MAX);
setPotValue(LOW_CS_PIN, currentLowDB, EQ_MIN, EQ_MAX);
// --- Inicialización de la pantalla OLED ---
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(15, 24);
display.println("PATRO DJ");
display.display();
delay(1000);
// Forzar la primera actualización para mostrar el volumen al inicio
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
}
void loop() {
// 1. Encoder de VOLUMEN
long newVol = encoderVol.getCount() / ENCODER_STEPS_PER_DB;
if (newVol > VOL_MAX) { newVol = VOL_MAX; encoderVol.setCount(newVol * ENCODER_STEPS_PER_DB); }
if (newVol < VOL_MIN) { newVol = VOL_MIN; encoderVol.setCount(newVol * ENCODER_STEPS_PER_DB); }
if (newVol != currentVolDB) {
currentVolDB = newVol;
//Serial.printf("VOLUMEN: %d dB\n", currentVolDB);
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
setPotValue(VOL_CS_PIN, currentVolDB, VOL_MIN, VOL_MAX); // <-- NUEVO: Actualizar pot
}
// 2. Encoder de GAIN
long newGain = encoderGain.getCount() / ENCODER_STEPS_PER_DB;
if (newGain > GAIN_MAX) { newGain = GAIN_MAX; encoderGain.setCount(newGain * ENCODER_STEPS_PER_DB); }
if (newGain < GAIN_MIN) { newGain = GAIN_MIN; encoderGain.setCount(newGain * ENCODER_STEPS_PER_DB); }
if (newGain != currentGainDB) {
currentGainDB = newGain;
//Serial.printf("GAIN: %d dB\n", currentGainDB);
actualizarPantalla("GAIN", currentGainDB, GAIN_MIN, GAIN_MAX);
setPotValue(GAIN_CS_PIN, currentGainDB, GAIN_MIN, GAIN_MAX); // <-- NUEVO: Actualizar pot
}
// 3. Encoder de HIGH
long newHigh = encoderHigh.getCount() / ENCODER_STEPS_PER_DB;
if (newHigh > EQ_MAX) { newHigh = EQ_MAX; encoderHigh.setCount(newHigh * ENCODER_STEPS_PER_DB); }
if (newHigh < EQ_MIN) { newHigh = EQ_MIN; encoderHigh.setCount(newHigh * ENCODER_STEPS_PER_DB); }
if (newHigh != currentHighDB) {
currentHighDB = newHigh;
//Serial.printf("HIGH: %d dB\n", currentHighDB);
actualizarPantalla("HIGH", currentHighDB, EQ_MIN, EQ_MAX);
setPotValue(HIGH_CS_PIN, currentHighDB, EQ_MIN, EQ_MAX); // <-- NUEVO: Actualizar pot
}
// 4. Encoder de MID HIGH
long newMidHigh = encoderMidHigh.getCount() / ENCODER_STEPS_PER_DB;
if (newMidHigh > EQ_MAX) { newMidHigh = EQ_MAX; encoderMidHigh.setCount(newMidHigh * ENCODER_STEPS_PER_DB); }
if (newMidHigh < EQ_MIN) { newMidHigh = EQ_MIN; encoderMidHigh.setCount(newMidHigh * ENCODER_STEPS_PER_DB); }
if (newMidHigh != currentMidHighDB) {
currentMidHighDB = newMidHigh;
//Serial.printf("MID HIGH: %d dB\n", currentMidHighDB);
actualizarPantalla("MID HIGH", currentMidHighDB, EQ_MIN, EQ_MAX);
setPotValue(MID_HIGH_CS_PIN, currentMidHighDB, EQ_MIN, EQ_MAX); // <-- NUEVO: Actualizar pot
}
// 5. Encoder de MID LOW
long newMidLow = encoderMidLow.getCount() / ENCODER_STEPS_PER_DB;
if (newMidLow > EQ_MAX) { newMidLow = EQ_MAX; encoderMidLow.setCount(newMidLow * ENCODER_STEPS_PER_DB); }
if (newMidLow < EQ_MIN) { newMidLow = EQ_MIN; encoderMidLow.setCount(newMidLow * ENCODER_STEPS_PER_DB); }
if (newMidLow != currentMidLowDB) {
currentMidLowDB = newMidLow;
//Serial.printf("MID LOW: %d dB\n", currentMidLowDB);
actualizarPantalla("MID LOW", currentMidLowDB, EQ_MIN, EQ_MAX);
setPotValue(MID_LOW_CS_PIN, currentMidLowDB, EQ_MIN, EQ_MAX); // <-- NUEVO: Actualizar pot
}
// 6. Encoder de LOW
long newLow = encoderLow.getCount() / ENCODER_STEPS_PER_DB;
if (newLow > EQ_MAX) { newLow = EQ_MAX; encoderLow.setCount(newLow * ENCODER_STEPS_PER_DB); }
if (newLow < EQ_MIN) { newLow = EQ_MIN; encoderLow.setCount(newLow * ENCODER_STEPS_PER_DB); }
if (newLow != currentLowDB) {
currentLowDB = newLow;
//Serial.printf("LOW: %d dB\n", currentLowDB);
actualizarPantalla("LOW", currentLowDB, EQ_MIN, EQ_MAX);
setPotValue(LOW_CS_PIN, currentLowDB, EQ_MIN, EQ_MAX); // <-- NUEVO: Actualizar pot
}
delay(20);
}
// Envía un comando de escritura al MCP4231/503
// csPin: El pin Chip Select del IC a controlar
// address: La dirección del registro (0x00 para Wiper 0, 0x10 para Wiper 1)
// value: El valor de 8 bits (0-255) a escribir en el potenciómetro
void writeToPot(int csPin, byte address, byte value) {
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); // Iniciar transacción SPI (1MHz, MSB first, Mode 0)
digitalWrite(csPin, LOW); // Seleccionar el chip
SPI.transfer(address); // Enviar byte de comando/dirección
SPI.transfer(value); // Enviar byte de datos
digitalWrite(csPin, HIGH); // Deseleccionar el chip
SPI.endTransaction(); // Finalizar transacción
}
// --- Convierte dB a valor de potenciómetro y lo envía
void setPotValue(int csPin, int dbValue, int dbMin, int dbMax) {
// Mapeamos el rango de dB al rango del potenciómetro (0-255).
// ¡IMPORTANTE! Para atenuar una señal, el valor mínimo en dB (ej: -80) debe
// corresponder a la máxima resistencia (paso 255), y el valor máximo de dB (ej: 0)
// debe corresponder a la mínima resistencia (paso 0). Por eso invertimos el rango de salida.
long potStep = map(dbValue, dbMin, dbMax, POT_MAX_STEPS, 0);
// Asegurarnos de que el valor está en el rango correcto
potStep = constrain(potStep, 0, POT_MAX_STEPS);
//Serial.printf(" [SPI] CS Pin: %d -> dB: %d -> Pot Step: %ld\n", csPin, dbValue, potStep);
// Escribimos en el Potenciómetro 0 (Wiper 0) del chip seleccionado.
// La dirección para Wiper 0 es 0x00.
writeToPot(csPin, 0x00, (byte)potStep);
// OPCIONAL: Si quisieras controlar un canal estéreo, podrías escribir
// el mismo valor en el Potenciómetro 1 (Wiper 1), cuya dirección es 0x10.
writeToPot(csPin, 0x10, (byte)potStep);
}
// --- Función para actualizar la pantalla (sin cambios) ---
void actualizarPantalla(const char *paramName, int currentValue, int minValue, int maxValue) {
display.clearDisplay();
// 1. Dibuja el nombre y el valor numérico
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(paramName, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 2);
display.println(paramName);
char dbStr[10];
sprintf(dbStr, "%+d dB", currentValue);
display.getTextBounds(dbStr, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 20);
display.print(dbStr);
// 2. Dibuja la barra de progreso
int barWidth = map(currentValue, minValue, maxValue, 0, SCREEN_WIDTH);
display.drawRect(0, 42, SCREEN_WIDTH, 16, SSD1306_WHITE);
if (minValue < 0 && maxValue > 0) {
int zeroPoint = map(0, minValue, maxValue, 0, SCREEN_WIDTH);
display.drawFastVLine(zeroPoint, 40, 20, SSD1306_WHITE);
if (barWidth > zeroPoint) {
display.fillRect(zeroPoint, 42, barWidth - zeroPoint, 16, SSD1306_WHITE);
} else {
display.fillRect(barWidth, 42, zeroPoint - barWidth, 16, SSD1306_WHITE);
}
} else {
display.fillRect(0, 42, barWidth, 16, SSD1306_WHITE);
}
// 3. Muestra todo en la pantalla
display.display();
}
*/
//V2
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Encoder.h>
#include <SPI.h>
// --- Configuración de la Pantalla ---
// --- SCL=22, SDA=21
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- Configuración de Pines de Encoders ---
#define VOL_CLK_PIN 12
#define VOL_DT_PIN 13
#define GAIN_CLK_PIN 1 //TX
#define GAIN_DT_PIN 3 //RX
#define HIGH_CLK_PIN 34
#define HIGH_DT_PIN 35
#define MID_HIGH_CLK_PIN 32
#define MID_HIGH_DT_PIN 33
#define MID_LOW_CLK_PIN 25
#define MID_LOW_DT_PIN 26
#define LOW_CLK_PIN 27
#define LOW_DT_PIN 14
// --- Pines Chip Select (CS) para MCP4231/503
// --- Pines SPI: MOSI=23, MISO=19, SCK=18
#define VOL_CS_PIN 15
#define GAIN_CS_PIN 5 //CS/SS
#define HIGH_CS_PIN 17 //TX2
#define MID_HIGH_CS_PIN 16 //RX2
#define MID_LOW_CS_PIN 4
#define LOW_CS_PIN 2
// --- Rangos de Decibelios (dB)
const int VOL_MIN = -80, VOL_MAX = 0;
const int GAIN_MIN = -20, GAIN_MAX = 20;
const int EQ_MIN = -15, EQ_MAX = 15;
// --- Constantes para el Potenciómetro Digital
const int POT_MAX_STEPS = 255; // El MCP4231 tiene 256 pasos (0 a 255)
// --- Objetos Encoder ---
ESP32Encoder encoderVol;
ESP32Encoder encoderGain;
ESP32Encoder encoderHigh;
ESP32Encoder encoderMidHigh;
ESP32Encoder encoderMidLow;
ESP32Encoder encoderLow;
// --- Variables de estado para cada parámetro (en dB)
int currentVolDB = 0;
int currentGainDB = 0;
int currentHighDB = 0;
int currentMidHighDB = 0;
int currentMidLowDB = 0;
int currentLowDB = 0;
#define ENCODER_STEPS_PER_DB 2
// ***** NUEVAS VARIABLES PARA EL TIMEOUT DE LA PANTALLA *****
#define DISPLAY_TIMEOUT_MS 3000 // 3 segundos antes de volver a la pantalla de volumen
unsigned long lastInteractionTime = 0; // Guarda el tiempo de la última interacción
bool isDisplayingVolume = true; // Flag para saber si la pantalla ya muestra el volumen
// Prototipos de las nuevas funciones
void writeToPot(int csPin, byte address, byte value);
void setPotValue(int csPin, int dbValue, int dbMin, int dbMax);
void actualizarPantalla(const char *paramName, int currentValue, int minValue, int maxValue);
void setup() {
//Serial.begin(115200);
SPI.begin(); // Iniciar el bus SPI
// --- Inicialización de SPI y pines CS
pinMode(VOL_CS_PIN, OUTPUT);
pinMode(GAIN_CS_PIN, OUTPUT);
pinMode(HIGH_CS_PIN, OUTPUT);
pinMode(MID_HIGH_CS_PIN, OUTPUT);
pinMode(MID_LOW_CS_PIN, OUTPUT);
pinMode(LOW_CS_PIN, OUTPUT);
// Deseleccionar todos los chips al inicio (poner CS en ALTO)
digitalWrite(VOL_CS_PIN, HIGH);
digitalWrite(GAIN_CS_PIN, HIGH);
digitalWrite(HIGH_CS_PIN, HIGH);
digitalWrite(MID_HIGH_CS_PIN, HIGH);
digitalWrite(MID_LOW_CS_PIN, HIGH);
digitalWrite(LOW_CS_PIN, HIGH);
// --- Inicialización de Encoders ---
encoderVol.attachHalfQuad(VOL_DT_PIN, VOL_CLK_PIN);
encoderVol.setCount(VOL_MIN * ENCODER_STEPS_PER_DB);
currentVolDB = VOL_MIN;
encoderGain.attachHalfQuad(GAIN_DT_PIN, GAIN_CLK_PIN);
encoderGain.setCount(0 * ENCODER_STEPS_PER_DB);
currentGainDB = 0;
encoderHigh.attachHalfQuad(HIGH_DT_PIN, HIGH_CLK_PIN);
encoderHigh.setCount(0 * ENCODER_STEPS_PER_DB);
currentHighDB = 0;
encoderMidHigh.attachHalfQuad(MID_HIGH_DT_PIN, MID_HIGH_CLK_PIN);
encoderMidHigh.setCount(0 * ENCODER_STEPS_PER_DB);
currentMidHighDB = 0;
encoderMidLow.attachHalfQuad(MID_LOW_DT_PIN, MID_LOW_CLK_PIN);
encoderMidLow.setCount(0 * ENCODER_STEPS_PER_DB);
currentMidLowDB = 0;
encoderLow.attachHalfQuad(LOW_DT_PIN, LOW_CLK_PIN);
encoderLow.setCount(0 * ENCODER_STEPS_PER_DB);
currentLowDB = 0;
// --- Configurar el estado inicial de los potenciómetros
setPotValue(VOL_CS_PIN, currentVolDB, VOL_MIN, VOL_MAX);
setPotValue(GAIN_CS_PIN, currentGainDB, GAIN_MIN, GAIN_MAX);
setPotValue(HIGH_CS_PIN, currentHighDB, EQ_MIN, EQ_MAX);
setPotValue(MID_HIGH_CS_PIN, currentMidHighDB, EQ_MIN, EQ_MAX);
setPotValue(MID_LOW_CS_PIN, currentMidLowDB, EQ_MIN, EQ_MAX);
setPotValue(LOW_CS_PIN, currentLowDB, EQ_MIN, EQ_MAX);
// --- Inicialización de la pantalla OLED ---
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(15, 24);
display.println("PATRO DJ");
display.display();
delay(1000);
// Forzar la primera actualización para mostrar el volumen al inicio
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
isDisplayingVolume = true; // La pantalla empieza mostrando el volumen
lastInteractionTime = millis(); // Iniciar el temporizador
}
void loop() {
// 1. Encoder de VOLUMEN
long newVol = encoderVol.getCount() / ENCODER_STEPS_PER_DB;
if (newVol > VOL_MAX) { newVol = VOL_MAX; encoderVol.setCount(newVol * ENCODER_STEPS_PER_DB); }
if (newVol < VOL_MIN) { newVol = VOL_MIN; encoderVol.setCount(newVol * ENCODER_STEPS_PER_DB); }
if (newVol != currentVolDB) {
currentVolDB = newVol;
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
setPotValue(VOL_CS_PIN, currentVolDB, VOL_MIN, VOL_MAX);
// --- NUEVO: Actualizar temporizador y estado ---
lastInteractionTime = millis();
isDisplayingVolume = true;
}
// 2. Encoder de GAIN
long newGain = encoderGain.getCount() / ENCODER_STEPS_PER_DB;
if (newGain > GAIN_MAX) { newGain = GAIN_MAX; encoderGain.setCount(newGain * ENCODER_STEPS_PER_DB); }
if (newGain < GAIN_MIN) { newGain = GAIN_MIN; encoderGain.setCount(newGain * ENCODER_STEPS_PER_DB); }
if (newGain != currentGainDB) {
currentGainDB = newGain;
actualizarPantalla("GAIN", currentGainDB, GAIN_MIN, GAIN_MAX);
setPotValue(GAIN_CS_PIN, currentGainDB, GAIN_MIN, GAIN_MAX);
// --- NUEVO: Actualizar temporizador y estado ---
lastInteractionTime = millis();
isDisplayingVolume = false;
}
// 3. Encoder de HIGH
long newHigh = encoderHigh.getCount() / ENCODER_STEPS_PER_DB;
if (newHigh > EQ_MAX) { newHigh = EQ_MAX; encoderHigh.setCount(newHigh * ENCODER_STEPS_PER_DB); }
if (newHigh < EQ_MIN) { newHigh = EQ_MIN; encoderHigh.setCount(newHigh * ENCODER_STEPS_PER_DB); }
if (newHigh != currentHighDB) {
currentHighDB = newHigh;
actualizarPantalla("HIGH", currentHighDB, EQ_MIN, EQ_MAX);
setPotValue(HIGH_CS_PIN, currentHighDB, EQ_MIN, EQ_MAX);
// --- NUEVO: Actualizar temporizador y estado ---
lastInteractionTime = millis();
isDisplayingVolume = false;
}
// 4. Encoder de MID HIGH
long newMidHigh = encoderMidHigh.getCount() / ENCODER_STEPS_PER_DB;
if (newMidHigh > EQ_MAX) { newMidHigh = EQ_MAX; encoderMidHigh.setCount(newMidHigh * ENCODER_STEPS_PER_DB); }
if (newMidHigh < EQ_MIN) { newMidHigh = EQ_MIN; encoderMidHigh.setCount(newMidHigh * ENCODER_STEPS_PER_DB); }
if (newMidHigh != currentMidHighDB) {
currentMidHighDB = newMidHigh;
actualizarPantalla("MID HIGH", currentMidHighDB, EQ_MIN, EQ_MAX);
setPotValue(MID_HIGH_CS_PIN, currentMidHighDB, EQ_MIN, EQ_MAX);
// --- NUEVO: Actualizar temporizador y estado ---
lastInteractionTime = millis();
isDisplayingVolume = false;
}
// 5. Encoder de MID LOW
long newMidLow = encoderMidLow.getCount() / ENCODER_STEPS_PER_DB;
if (newMidLow > EQ_MAX) { newMidLow = EQ_MAX; encoderMidLow.setCount(newMidLow * ENCODER_STEPS_PER_DB); }
if (newMidLow < EQ_MIN) { newMidLow = EQ_MIN; encoderMidLow.setCount(newMidLow * ENCODER_STEPS_PER_DB); }
if (newMidLow != currentMidLowDB) {
currentMidLowDB = newMidLow;
actualizarPantalla("MID LOW", currentMidLowDB, EQ_MIN, EQ_MAX);
setPotValue(MID_LOW_CS_PIN, currentMidLowDB, EQ_MIN, EQ_MAX);
// --- NUEVO: Actualizar temporizador y estado ---
lastInteractionTime = millis();
isDisplayingVolume = false;
}
// 6. Encoder de LOW
long newLow = encoderLow.getCount() / ENCODER_STEPS_PER_DB;
if (newLow > EQ_MAX) { newLow = EQ_MAX; encoderLow.setCount(newLow * ENCODER_STEPS_PER_DB); }
if (newLow < EQ_MIN) { newLow = EQ_MIN; encoderLow.setCount(newLow * ENCODER_STEPS_PER_DB); }
if (newLow != currentLowDB) {
currentLowDB = newLow;
actualizarPantalla("LOW", currentLowDB, EQ_MIN, EQ_MAX);
setPotValue(LOW_CS_PIN, currentLowDB, EQ_MIN, EQ_MAX);
// --- NUEVO: Actualizar temporizador y estado ---
lastInteractionTime = millis();
isDisplayingVolume = false;
}
// ***** NUEVA LÓGICA DE TIMEOUT *****
// Comprueba si la pantalla NO está mostrando el volumen y si ha pasado el tiempo de espera
if (!isDisplayingVolume && (millis() - lastInteractionTime > DISPLAY_TIMEOUT_MS)) {
// Si se cumplen las condiciones, vuelve a mostrar la pantalla de volumen
actualizarPantalla("VOLUMEN", currentVolDB, VOL_MIN, VOL_MAX);
isDisplayingVolume = true; // Marcar que ahora sí se muestra el volumen
}
delay(20);
}
// Envía un comando de escritura al MCP4231/503
void writeToPot(int csPin, byte address, byte value) {
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(csPin, LOW);
SPI.transfer(address);
SPI.transfer(value);
digitalWrite(csPin, HIGH);
SPI.endTransaction();
}
// Convierte dB a valor de potenciómetro y lo envía
void setPotValue(int csPin, int dbValue, int dbMin, int dbMax) {
long potStep = map(dbValue, dbMin, dbMax, POT_MAX_STEPS, 0);
potStep = constrain(potStep, 0, POT_MAX_STEPS);
writeToPot(csPin, 0x00, (byte)potStep);
writeToPot(csPin, 0x10, (byte)potStep);
}
// Función para actualizar la pantalla (sin cambios)
void actualizarPantalla(const char *paramName, int currentValue, int minValue, int maxValue) {
display.clearDisplay();
// 1. Dibuja el nombre y el valor numérico
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(paramName, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 2);
display.println(paramName);
char dbStr[10];
sprintf(dbStr, "%+d dB", currentValue);
display.getTextBounds(dbStr, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 20);
display.print(dbStr);
// 2. Dibuja la barra de progreso
int barWidth = map(currentValue, minValue, maxValue, 0, SCREEN_WIDTH);
display.drawRect(0, 42, SCREEN_WIDTH, 16, SSD1306_WHITE);
if (minValue < 0 && maxValue > 0) {
int zeroPoint = map(0, minValue, maxValue, 0, SCREEN_WIDTH);
display.drawFastVLine(zeroPoint, 40, 20, SSD1306_WHITE);
if (barWidth > zeroPoint) {
display.fillRect(zeroPoint, 42, barWidth - zeroPoint, 16, SSD1306_WHITE);
} else {
display.fillRect(barWidth, 42, zeroPoint - barWidth, 16, SSD1306_WHITE);
}
} else {
display.fillRect(0, 42, barWidth, 16, SSD1306_WHITE);
}
// 3. Muestra todo en la pantalla
display.display();
}