Tenía un amplificador estéreo de los años 90 que sonaba muy bien pero no tenía mando a distancia. Añadirle uno con un chip PT2257 y un receptor infrarrojo costó menos de 8 euros y se programó en un fin de semana. Ahora uso el mando de la tele para subir, bajar y mutear sin levantarme del sofá.
01 — El amplificador que no tiene mando
El problema es sencillo: tienes un amplificador que suena bien pero que es tan viejo que no tiene mando a distancia, o al que directamente no le pusieron uno. Cada vez que quieres bajar el volumen tienes que levantarte. Solución clásica: meter un potenciómetro motorizado. Solución de friki con Arduino: interceptar la señal de volumen con un chip electrónico y controlarlo por infrarrojo.
La idea es poner el circuito entre la fuente de audio (el ordenador, el móvil, lo que sea) y la entrada de línea del amplificador. El PT2257 atenúa la señal en pasos de 1 dB, hasta un máximo de 79 dB de atenuación. El Arduino lee el mando por IR y le dice al PT2257 cuánto atenuar. El amplificador no se entera de nada: sigue recibiendo audio por sus entradas de siempre, solo que más o menos atenuado.
No toco el potenciómetro original del amplificador. Lo dejo en el máximo y el volumen real lo lleva el PT2257. Si quiero música máxima a tope, el Arduino pone atenuación 0 dB y listo.
02 — El PT2257: el chip de volumen electrónico
El PT2257 (o su hermano mayor el PT2258) es un chip de control de volumen electrónico que se comunica por I2C. Tiene dos canales (estéreo), puede atenuar de 0 a 79 dB en pasos de 1 dB, y tiene una función de mute por hardware. El ruido es bajísimo — se nota la diferencia respecto a un potenciómetro barato.
El protocolo I2C del PT2257 es sencillo: le mandas un byte de comando seguido de un byte de datos. Los comandos principales son:
0b11100000 → 2 canales, paso de -10 dB 0b11010000 → 2 canales, paso de -1 dB 0b01111000 → mute de 2 canales (bit 0 activa/desactiva) 0b11111111 → apagar (-79 dB)
Para poner -37 dB por ejemplo: primero mandas el comando de -10dB con el dato 3 (tres pasos de diez = -30 dB), luego el comando de -1dB con el dato 7 (siete pasos de uno = -7 dB). Sumado: -37 dB. El código hace exactamente esto con la función evc_level() que convierte un valor de 0–79 en el par de bytes que necesita el chip.
La dirección I2C por defecto del PT2257 es 0x88 (7 bits: 0x44). Algunos módulos la traen a 0x46. Puedes comprobarlo con el I2C Scanner de Arduino antes de compilar nada.
03 — Conexiones
Hay dos buses: I2C entre el Arduino y el PT2257, y el pin digital para el receptor IR. Nada más.
Conexiones en texto por si lo prefieres así:
Arduino Uno → PT2257 ─────────────────────────── 5V → VCC GND → GND A5 (SCL) → SCL A4 (SDA) → SDA Arduino Uno → KY-022 ─────────────────────────── 5V → VCC GND → GND D11 → OUT
Dos notas prácticas: primero, el I2C necesita resistencias pull-up en SCL y SDA (4,7 kΩ a 5V cada una), aunque muchos módulos PT2257 ya las traen soldadas en la placa. Segundo, el KY-022 es el sensor IR típico de los kits de Arduino, pero funciona cualquier receptor IR de 38 kHz: el VS1838B, el TSOP1738, lo que tengas a mano.
04 — Capturar los códigos IR del mando de la tele
Cada mando a distancia emite una secuencia de pulsos con su propio protocolo (NEC, Sony SIRC, RC5...) y sus propios códigos por botón. Para saber qué código emite tu mando al pulsar "volumen arriba", hay que escuchar y decodificar.
La librería IRremote incluye un sketch de ejemplo llamado IRrecvDemo o IRrecvDump que hace exactamente eso: escucha el receptor IR y vuelca por el puerto serie el código hexadecimal de cada botón que pulsas. Lo subes al Arduino, abres el monitor serie, apuntas el mando al receptor y pulsas los botones que quieras mapear. Apuntas los hexadecimales que aparecen.
En mi caso, con el mando de mi tele LG:
Botón Código HEX ──────────────────────────────── Volumen + 0x68733A46 Volumen - 0x83B19366 Mute 0x2340B922
Esos tres valores son los que van en las tres #define al principio del sketch. Si usas otro mando, cambias esos tres números y ya. No hay que tocar nada más.
05 — El código
El sketch tiene tres capas: la librería IRremote que decodifica los pulsos del receptor, el bus I2C que habla con el PT2257, y la lógica de volumen que convierte "subir volumen" en "atenuar 1 dB menos".
El PT2257 trabaja con atenuación (cuánto se quita, no cuánto se añade), así que el valor 0 es volumen máximo y el 79 es silencio total. Para "subir volumen" la variable volumenActual baja un punto; para "bajar volumen" sube. Al mute, le manda el comando de mute por hardware del chip, que es más limpio que atenuar a 79.
La función evc_setVolume() hace la conversión: separa los dB en decenas y unidades porque el PT2257 los recibe en dos bytes distintos — un comando para los -10 dB/paso y otro para los -1 dB/paso:
// PakuVol 1.0a
#include <IRremote.h> // KY-022 (IR)
#include <Wire.h> // I2C (PT2257)
#define PT2257_ADDR 0x44 // dirección I2C (0x88 en algunos módulos)
#define EVC_OFF 0b11111111 // apagar (-79dB)
#define EVC_2CH_1 0b11010000 // 2 canales, -1dB/paso
#define EVC_2CH_10 0b11100000 // 2 canales, -10dB/paso
#define EVC_MUTE 0b01111000 // mute de 2 canales
#define mutear 0x2340B922 // código IR del botón mute
#define volumenUP 0x68733A46 // código IR del botón volumen +
#define volumenDOWN 0x83B19366 // código IR del botón volumen -
int pinReceptorIR = 11;
IRrecv irrecv(pinReceptorIR);
bool estoyMuteado;
uint8_t volumenActual; // atenuación actual en dB (0=máximo, 79=silencio)
void setup() {
Serial.begin(9600);
delay(200);
irrecv.enableIRIn();
// Parpadeo del LED de encendido para confirmar arranque
for (int i = 0; i < 2; i++) {
digitalWrite(LED_BUILTIN, HIGH); delay(500);
digitalWrite(LED_BUILTIN, LOW); delay(500);
}
Wire.begin();
evc_setVolume(0); // volumen al máximo al arrancar
estoyMuteado = false;
volumenActual = 0;
}
void loop() {
decode_results results;
if (irrecv.decode(&results)) {
switch (results.value) {
case volumenUP:
if (volumenActual > 0) volumenActual--;
evc_setVolume(volumenActual);
break;
case volumenDOWN:
if (volumenActual < 79) volumenActual++;
evc_setVolume(volumenActual);
break;
case mutear:
estoyMuteado = !estoyMuteado;
evc_mute(estoyMuteado);
delay(200);
break;
}
irrecv.resume();
}
}
// Convierte dB (0-79) en el par de bytes que espera el PT2257
byte evc_level(uint8_t dB) {
if (dB > 79) dB = 79;
uint8_t b = dB / 10; // decenas (comando -10dB/paso)
uint8_t a = dB % 10; // unidades (comando -1dB/paso)
return ((b & 0b111) << 4) | a;
}
void evc_setVolume(uint8_t dB) {
byte bbbaaaa = evc_level(dB);
Wire.beginTransmission(PT2257_ADDR);
Wire.write(EVC_2CH_10 | ((bbbaaaa >> 4) & 0xF)); // decenas
Wire.write(EVC_2CH_1 | (bbbaaaa & 0xF)); // unidades
Wire.endTransmission();
}
void evc_mute(bool mutea) {
Wire.beginTransmission(PT2257_ADDR);
Wire.write(EVC_MUTE | (mutea & 0x01));
Wire.endTransmission();
}
Una cosa que hay que tener en cuenta: la librería IRremote en versiones antiguas (la 2.x) tiene la función irrecv.decode(&results) con puntero. Las versiones modernas (3.x y 4.x) cambiaron la API completamente — si el IDE te da error de compilación, comprueba qué versión tienes instalada en el gestor de librerías. El código de arriba es para la v2.x, que es la que venía en los kits de 2017.
06 — Montaje final
Para una instalación definitiva lo ideal es hacer una PCB pequeña o meterlo en una caja de proyecto. Yo lo dejé en protoboard dentro de una caja de plástico con el receptor IR saliendo por un agujero lateral. Lleva funcionando así desde 2017 sin un solo problema.
El Arduino se alimenta por USB desde un cargador de pared de los de móvil. El PT2257 toma los 5V del propio Arduino. El audio entra y sale por jack 3,5mm (o RCA, lo que sea que use el amplificador). El único cable de señal que toca el PT2257 son las entradas y salidas de audio — el resto es I2C y alimentación.
El resultado es exactamente lo que quería: subir y bajar volumen desde el sofá con el mando de la tele sin ningún tipo de latencia perceptible, mute instantáneo, y arranca en el último volumen que tenía gracias a que lo guardo en volumenActual... bueno, en realidad no lo guardo en EEPROM, así que al apagarlo y encenderlo vuelve a 0 dB (volumen máximo). Para la siguiente versión toca añadir EEPROM.write().