/*
Necesitarás dos chips 74HC4067 (o similar).
Multiplexor 1 (para pines CLK):
Conecta el pin Y0 a la señal CLK del encoder de Volumen.
Conecta el pin Y1 a la señal CLK del encoder de Ganancia.
Conecta el pin Y2 a la señal CLK del encoder de Agudos (High).
...y así sucesivamente hasta el Y5 para el encoder de Graves (Low).
Conecta su pin de salida Z al pin MUX_CLK_OUT_PIN del ESP32 (GPIO 12 en el código).
Multiplexor 2 (para pines DT):
Conecta el pin Y0 a la señal DT del encoder de Volumen.
Conecta el pin Y1 a la señal DT del encoder de Ganancia.
Conecta el pin Y2 a la señal DT del encoder de Agudos (High).
...y así sucesivamente hasta el Y5 para el encoder de Graves (Low).
Conecta su pin de salida Z al pin MUX_DT_OUT_PIN del ESP32 (GPIO 13 en el código).
Conexiones Comunes para ambos Multiplexores:
Conecta los pines S0, S1, S2, S3 de ambos chips juntos.
Conecta el S0 común al MUX_S0_PIN del ESP32 (GPIO 25).
Conecta el S1 común al MUX_S1_PIN del ESP32 (GPIO 26).
Conecta el S2 común al MUX_S2_PIN del ESP32 (GPIO 27).
Conecta el S3 común al MUX_S3_PIN del ESP32 (GPIO 14).
Conecta los pines EN (Enable) de ambos chips a GND para mantenerlos siempre activos.
Conecta VCC a 3.3V y GND a tierra.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>
// --- Configuración de la Pantalla ---
// --- Pines: SDA=21, SCL=22
#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);
// --- NUEVO: Configuración de Pines para Multiplexor ---
// Necesitarás dos multiplexores 74HC4067. Uno para los CLK y otro para los DT.
// Los pines S0-S3 de ambos multiplexores se conectan juntos a estos pines del ESP32.
#define MUX_S0_PIN 14 // Pin de selección 0
#define MUX_S1_PIN 27 // Pin de selección 1
#define MUX_S2_PIN 26 // Pin de selección 2
#define MUX_S3_PIN 25 // Pin de selección 3 (No se usa con 6 encoders, pero se define por si acaso)
// Estos dos pines leen la salida 'Z' de cada multiplexor.
#define MUX_CLK_OUT_PIN 12 // Conectado a la salida del MUX de los CLK
#define MUX_DT_OUT_PIN 13 // Conectado a la salida del MUX de los DT
// --- Configuración de Pines Chip Select (CS) para MCP4231 (sin cambios) ---
//----Pines SPI: MOSI=23, 19=MISO, SCK=18
#define VOL_CS_PIN 15
#define GAIN_CS_PIN 5
#define HIGH_CS_PIN 17
#define MID_HIGH_CS_PIN 16
#define MID_LOW_CS_PIN 4
#define LOW_CS_PIN 2
// --- Constantes (sin cambios) ---
const int POT_MAX_STEPS = 255;
#define ENCODER_STEPS_PER_DB 2 // Ajusta la sensibilidad del encoder
#define NUM_ENCODERS 6
// --- Arrays para gestionar todos los parámetros de forma centralizada ---
// Nombres para la pantalla
const char* PARAM_NAMES[NUM_ENCODERS] = {"VOLUMEN", "GAIN", "HIGH", "MID HIGH", "MID LOW", "LOW"};
// Pines Chip Select para cada potenciómetro
const int CS_PINS[NUM_ENCODERS] = {VOL_CS_PIN, GAIN_CS_PIN, HIGH_CS_PIN, MID_HIGH_CS_PIN, MID_LOW_CS_PIN, LOW_CS_PIN};
// Valores mínimos en dB
const int DB_MINS[NUM_ENCODERS] = {-80, -10, -15, -15, -15, -15};
// Valores máximos en dB
const int DB_MAXS[NUM_ENCODERS] = {0, 40, 15, 15, 15, 15};
// --- Variables de estado para el sondeo de encoders ---
long encoderCounts[NUM_ENCODERS] = {0};
int currentDBValues[NUM_ENCODERS] = {0};
int lastEncoded[NUM_ENCODERS] = {0}; // Almacena el último estado (CLK|DT) de cada encoder
// Prototipos de 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 selectEncoder(byte encoderIndex);
void readAllEncoders();
void setup() {
Serial.begin(115200);
// --- Inicialización de SPI y pines CS ---
for (int i = 0; i < NUM_ENCODERS; i++) {
pinMode(CS_PINS[i], OUTPUT);
digitalWrite(CS_PINS[i], HIGH); // Deseleccionar todos
}
SPI.begin();
// --- NUEVO: Inicialización de pines del Multiplexor ---
pinMode(MUX_S0_PIN, OUTPUT);
pinMode(MUX_S1_PIN, OUTPUT);
pinMode(MUX_S2_PIN, OUTPUT);
pinMode(MUX_S3_PIN, OUTPUT);
pinMode(MUX_CLK_OUT_PIN, INPUT_PULLUP); // Usar PULLUP interno para estabilidad
pinMode(MUX_DT_OUT_PIN, INPUT_PULLUP); // Usar PULLUP interno para estabilidad
// --- 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);
// --- Inicialización de valores iniciales ---
Serial.println("Configurando estado inicial...");
// Volumen (Índice 0)
currentDBValues[0] = DB_MINS[0];
encoderCounts[0] = currentDBValues[0] * ENCODER_STEPS_PER_DB;
// Resto de parámetros (Índices 1 a 5) inician en 0 dB
for (int i = 1; i < NUM_ENCODERS; i++) {
currentDBValues[i] = 0;
encoderCounts[i] = 0 * ENCODER_STEPS_PER_DB;
}
// --- Configurar el estado inicial de los potenciómetros ---
for (int i = 0; i < NUM_ENCODERS; i++) {
setPotValue(CS_PINS[i], currentDBValues[i], DB_MINS[i], DB_MAXS[i]);
}
// Forzar la primera actualización para mostrar el volumen al inicio
actualizarPantalla(PARAM_NAMES[0], currentDBValues[0], DB_MINS[0], DB_MAXS[0]);
}
void loop() {
// 1. Leer todos los encoders muy rápidamente
readAllEncoders();
// 2. Comprobar si algún valor ha cambiado y actuar en consecuencia
for (int i = 0; i < NUM_ENCODERS; i++) {
long newDB = encoderCounts[i] / ENCODER_STEPS_PER_DB;
// Limitar el valor dentro de su rango
if (newDB > DB_MAXS[i]) { newDB = DB_MAXS[i]; encoderCounts[i] = newDB * ENCODER_STEPS_PER_DB; }
if (newDB < DB_MINS[i]) { newDB = DB_MINS[i]; encoderCounts[i] = newDB * ENCODER_STEPS_PER_DB; }
// Si el valor en dB ha cambiado, actualizamos todo
if (newDB != currentDBValues[i]) {
currentDBValues[i] = newDB;
Serial.printf("%s: %d dB\n", PARAM_NAMES[i], currentDBValues[i]);
// Actualizar pantalla y potenciómetro
actualizarPantalla(PARAM_NAMES[i], currentDBValues[i], DB_MINS[i], DB_MAXS[i]);
setPotValue(CS_PINS[i], currentDBValues[i], DB_MINS[i], DB_MAXS[i]);
}
}
delay(1); // Pequeña pausa para no saturar el loop
}
// --- NUEVA FUNCIÓN: Selecciona qué encoder se va a leer a través del MUX ---
void selectEncoder(byte encoderIndex) {
// Envía la dirección binaria del encoder a los pines de selección del MUX
digitalWrite(MUX_S0_PIN, (encoderIndex & 1)); // Bit 0
digitalWrite(MUX_S1_PIN, (encoderIndex & 2) >> 1); // Bit 1
digitalWrite(MUX_S2_PIN, (encoderIndex & 4) >> 2); // Bit 2
digitalWrite(MUX_S3_PIN, (encoderIndex & 8) >> 3); // Bit 3
delayMicroseconds(50); // Pequeña espera para que la señal del MUX se estabilice
}
// --- NUEVA FUNCIÓN: Lee todos los encoders mediante sondeo (polling) ---
void readAllEncoders() {
for (int i = 0; i < NUM_ENCODERS; i++) {
selectEncoder(i); // Apunta el MUX al encoder 'i'
// Leemos el estado de los pines CLK y DT para este encoder
int MSB = digitalRead(MUX_CLK_OUT_PIN); // MSB = most significant bit
int LSB = digitalRead(MUX_DT_OUT_PIN); // LSB = least significant bit
int encoded = (MSB << 1) | LSB; // Combina los dos bits en un número de 2 bits
// Comparamos con el último estado conocido para detectar un cambio
int sum = (lastEncoded[i] << 2) | encoded;
// Usamos una tabla de transición de estados para determinar la dirección
// Esto es estándar para encoders de cuadratura
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {
encoderCounts[i]++; // Girando en sentido horario
}
if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
encoderCounts[i]--; // Girando en sentido antihorario
}
lastEncoded[i] = encoded; // Guardamos el estado actual para la próxima lectura
}
}
// --- Función de escritura a potenciómetro SPI (sin cambios) ---
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();
}
// --- Función de conversión dB a valor de potenciómetro (sin cambios) ---
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);
Serial.printf(" [SPI] CS Pin: %d -> dB: %d -> Pot Step: %ld\n", csPin, dbValue, potStep);
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();
}
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>
// --- Configuración de la Pantalla ---
// --- Pines: SDA=21, SCL=22
#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 para Multiplexor ---
#define MUX_S0_PIN 14 // Pin de selección 0
#define MUX_S1_PIN 27 // Pin de selección 1
#define MUX_S2_PIN 26 // Pin de selección 2
#define MUX_S3_PIN 25 // Pin de selección 3
#define MUX_CLK_OUT_PIN 12 // Conectado a la salida del MUX de los CLK (mux2 en el JSON)
#define MUX_DT_OUT_PIN 13 // Conectado a la salida del MUX de los DT (mux1 en el JSON)
// --- Configuración de Pines Chip Select (CS) para MCP4231 ---
//----Pines SPI: MOSI=23, 19=MISO, SCK=18
#define VOL_CS_PIN 15
#define GAIN_CS_PIN 5
#define HIGH_CS_PIN 17
#define MID_HIGH_CS_PIN 16
#define MID_LOW_CS_PIN 4
#define LOW_CS_PIN 2
// --- Constantes ---
const int POT_MAX_STEPS = 255;
#define ENCODER_STEPS_PER_DB 2
#define NUM_ENCODERS 6
// --- Arrays para gestionar todos los parámetros de forma centralizada ---
const char* PARAM_NAMES[NUM_ENCODERS] = {"VOLUMEN", "GAIN", "HIGH", "MID HIGH", "MID LOW", "LOW"};
const int CS_PINS[NUM_ENCODERS] = {VOL_CS_PIN, GAIN_CS_PIN, HIGH_CS_PIN, MID_HIGH_CS_PIN, MID_LOW_CS_PIN, LOW_CS_PIN};
const int DB_MINS[NUM_ENCODERS] = {-80, -10, -15, -15, -15, -15};
const int DB_MAXS[NUM_ENCODERS] = {0, 40, 15, 15, 15, 15};
// Mapa de conexión del MUX al índice lógico del encoder
const int muxChannelToEncoderIndex[NUM_ENCODERS] = {5, 3, 0, 1, 2, 4};
// --- Variables de estado ---
volatile long encoderCounts[NUM_ENCODERS] = {0};
int currentDBValues[NUM_ENCODERS] = {0};
volatile int lastEncoded[NUM_ENCODERS] = {0};
// Prototipos
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 selectEncoder(byte encoderIndex);
void readAllEncoders();
void setup() {
Serial.begin(115200);
for (int i = 0; i < NUM_ENCODERS; i++) {
pinMode(CS_PINS[i], OUTPUT);
digitalWrite(CS_PINS[i], HIGH);
}
SPI.begin();
pinMode(MUX_S0_PIN, OUTPUT);
pinMode(MUX_S1_PIN, OUTPUT);
pinMode(MUX_S2_PIN, OUTPUT);
pinMode(MUX_S3_PIN, OUTPUT);
pinMode(MUX_CLK_OUT_PIN, INPUT_PULLUP);
pinMode(MUX_DT_OUT_PIN, INPUT_PULLUP);
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);
Serial.println("Configurando estado inicial...");
currentDBValues[0] = DB_MINS[0];
encoderCounts[0] = currentDBValues[0] * ENCODER_STEPS_PER_DB;
for (int i = 1; i < NUM_ENCODERS; i++) {
currentDBValues[i] = 0;
encoderCounts[i] = 0 * ENCODER_STEPS_PER_DB;
}
for (int i = 0; i < NUM_ENCODERS; i++) {
setPotValue(CS_PINS[i], currentDBValues[i], DB_MINS[i], DB_MAXS[i]);
}
actualizarPantalla(PARAM_NAMES[0], currentDBValues[0], DB_MINS[0], DB_MAXS[0]);
}
void loop() {
readAllEncoders();
for (int i = 0; i < NUM_ENCODERS; i++) {
long newDB = encoderCounts[i] / ENCODER_STEPS_PER_DB;
if (newDB > DB_MAXS[i]) { newDB = DB_MAXS[i]; encoderCounts[i] = newDB * ENCODER_STEPS_PER_DB; }
if (newDB < DB_MINS[i]) { newDB = DB_MINS[i]; encoderCounts[i] = newDB * ENCODER_STEPS_PER_DB; }
if (newDB != currentDBValues[i]) {
currentDBValues[i] = newDB;
Serial.printf("%s: %d dB\n", PARAM_NAMES[i], currentDBValues[i]);
actualizarPantalla(PARAM_NAMES[i], currentDBValues[i], DB_MINS[i], DB_MAXS[i]);
setPotValue(CS_PINS[i], currentDBValues[i], DB_MINS[i], DB_MAXS[i]);
}
}
delay(1);
}
void selectEncoder(byte encoderIndex) {
digitalWrite(MUX_S0_PIN, (encoderIndex & 1));
digitalWrite(MUX_S1_PIN, (encoderIndex & 2) >> 1);
digitalWrite(MUX_S2_PIN, (encoderIndex & 4) >> 2);
digitalWrite(MUX_S3_PIN, (encoderIndex & 8) >> 3);
delayMicroseconds(2);
}
void readAllEncoders() {
for (int muxChannel = 0; muxChannel < NUM_ENCODERS; muxChannel++) {
selectEncoder(muxChannel);
int logicalIndex = muxChannelToEncoderIndex[muxChannel];
// --- CAMBIO FINAL Y DEFINITIVO ---
// La tabla de transiciones usada espera que el valor se forme como (DT, CLK).
// Por lo tanto, DT debe ser el bit más significativo (MSB).
int MSB = digitalRead(MUX_DT_OUT_PIN); // Leemos DT primero
int LSB = digitalRead(MUX_CLK_OUT_PIN); // Leemos CLK después
int encoded = (MSB << 1) | LSB;
int sum = (lastEncoded[logicalIndex] << 2) | encoded;
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {
encoderCounts[logicalIndex]++;
}
if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
encoderCounts[logicalIndex]--;
}
lastEncoded[logicalIndex] = encoded;
}
}
// El resto de las funciones permanecen sin cambios
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();
}
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);
}
void actualizarPantalla(const char *paramName, int currentValue, int minValue, int maxValue) {
display.clearDisplay();
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);
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);
}
display.display();
}