hardware 2017 · un fin de semana · ~8 €

PakuVol: mando a distancia de volumen con Arduino y el mando de la tele

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á.

Primer prototipo del PakuVol en protoboard con Arduino Uno y chip PT2257
Primer prototipo en protoboard. El Arduino Uno en la parte superior, el PT2257 en el centro de la breadboard y el receptor IR KY-022 a la derecha.

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.

Datasheet PT2257 (PDF)

03 — Conexiones

Hay dos buses: I2C entre el Arduino y el PT2257, y el pin digital para el receptor IR. Nada más.

PakuVol — conexiones Arduino Uno, PT2257 y receptor IR KY-022 PAKUVOL — CONEXIONES ARDUINO UNO 5V GND A5 / SCL A4 / SDA D11 PT2257 EVC I2C — 0x44 VCC (5V) GND SCL SDA KY-022 Receptor IR OUT → D11 VCC GND 5V GND SCL SDA IR OUT --- alimentación compartida
Esquema de conexiones. El bus I2C (SCL/SDA) conecta el Arduino Uno con el PT2257. El receptor IR KY-022 va al pin digital 11. VCC y GND del KY-022 comparten la misma línea de alimentación del bus (línea discontinua).

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.

Datasheet KY-022 (PDF)

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

Circuito PakuVol montado con Arduino Uno y PT2257 conectados
El circuito ya cableado. El PT2257 en DIP-16 en la protoboard, el I2C con los cables azul (SCL) y verde (SDA), y el receptor IR a la derecha.
Detalle del chip PT2257 en la protoboard con los cables I2C
Detalle del PT2257. Los pines de audio (entradas y salidas de los dos canales) van al amplificador; los pines I2C van al Arduino.
Montaje final del PakuVol integrado junto al amplificador
El montaje integrado junto al amplificador. El Arduino alimentado por USB, el PT2257 en su plaquita, y el receptor IR asomando hacia donde está el sofá.

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().