Updated:
Cómo acoplar un mando a distancia en Arduino
Puedes leer primero Cómo funciona un mando a distancia
Hardware
Material:
- Placa Arduino (ATmega8) y de prototipos
- Chip [http://www.alldatasheet.com/datasheet-pdf/pdf/26102/TEMIC/TFMS5360.html TFMS5560] (Telefunken). Hay decenas de chips receptores de IR, no tiene por qué ser este en concreto, puede ser cualquier otro, eso sí, con filtro paso-banda en 56kHz a ser posible
- Cables de conexión
Código de línea del telemando de la Xbox
LA SIGUIENTE INFORMACIÓN ES ESPECÍFICA PARA EL MANDO A DISTANCIA DE LA XBOX. SI TIENES UN MANDO DE OTRA MARCA O MODELO, LEE MÁS ABAJO. He elegido este mando porque me parece que es bastante popular y además su código de línea (modulación digital en banda base) es sencillo.No todos los telemandos IR se decodifican con la misma facilidad, los hay que envían códigos de línea bastante puñeteros, usan códigos especiales cuando una tecla permanece pulsada y otras mezquindades.... Además, es uno de los mandos que tenía en casa muertos de risa, algo había que hacer con él... [[Image:Ir_signal_xbox.png|600px|center|]] Cada vez que pulsamos una tecla del mando, el TFMS5560 procesa la señal recibida Lo que hace es bajar la señal a banda base, que esta doblemente mezclada: primero suprime la portadora óptica (la luz IR) mediante un fotodiodo PIN y luego la portadora eléctrica (señal cuadrada de frecuencia 56kHz) mediante un integrador y un filtro paso banda y entrega en el terminal una secuencia de pulsos y espacios como la que aparece en la figuraEn realidad es la inversa de la que muestra la figura porque la salida es activa con nivel bajo, con 13 flancos de subida y 13 de bajada, alternados, lógicamente. La distancia entre dos flancos consecutivos (que llamaremos ranura), determina si el bit es "0" o "1". * La primera ranura es el pulso inicial AGC (Automatic Gain Control) necesario para estabilizar el control automático de ganancia del TFMS5560 y no representa ningún bit de información. * A continuación llega el código de 24 bits de la tecla pulsadaMANDO A DISTANCIA DE LA XBOX (DVDKit) Frecuencia de la portadora: 56 kHz Duración del pulso inicial AGC: 4500 us Duración de la ranura correspondiente al bit 0: 1500 us Duración de la ranura correspondiente al bit 1: 2500 us Número de bits/trama: 24 Código de cada tecla: Tecla Código de tecla ----------------------------- DISPLAY 0x52AAD5 REVERSE 0x51DAE2 PLAY 0x515AEA FORWARD 0x51CAE3 SKIP- 0x522ADD STOP 0x51FAE0 PAUSE 0x519AE6 SKIP+ 0x520ADF TITLE 0x51AAE5 INFO 0x53CAC3 MENU 0x508AF7 BACK 0x527AD8 UP 0x559AA6 DOWN 0x558AA7 LEFT 0x556AA9 RIGHT 0x557AA8 SELECT 0x5F4A0B 1 0x531ACE 2 0x532ACD 3 0x533ACC 4 0x534ACB 5 0x535ACA 6 0x536AC9 7 0x537AC8 8 0x538AC7 9 0x539AC6 0 0x530ACF
Software
Parece que la forma más conveniente de demodular este código de línea (puedo estar equivocado) es usar la interrupción externa INT0 y la del temporizador TMR2. No parece factible hacer esto por sondeo ni usar bucles de espera activa porque consumiría buena parte de los recursos del micro (y se supone que el receptor de IR va a ser sólo una pequeña parte del proyecto total). '''La interrupción INT0'''Salta cada vez que hay un flanco de bajada o de subida en el pin 2 de Arduino. Esta rutina de servicio a la interrupción es básicamente una máquina de estados cuya variable de estado (variable
bit
) es el número bit que estamos recibiendo (hay un pulso inicial y luego 24 bits).
La rutina comprueba el número de bit que estamos recibiendo, mide la duración de la ranura y decide si es "0" ó "1". Cuando llegamos al bit 24, el código de tecla queda almacenado en la variable ir_cmd
.
'''La interrupción TMR2'''La rutina anterior necesita poder medir el tiempo. Con una resolución de 16us es más que suficiente. Para esto lo mejor es usar la interrupción del temporizador TMR2 configurado en modo normal y sin divisor de reloj (preescaler = 1) para que dispare cada 16us e incremente la variable
usec
'''ALTERNATIVA'''Otra posibilidad sería usar únicamente el temporizador TMR2, muestrear periódicamente la señal de salida del chip y demodular. Así nos ahorramos la interrupción externa INT0. Aun así, he elegido la primera opción en lugar de esta porque me parece más clara, más pedagógica ;-)
PREESCALER * TIMERCOUNT / F_CLK = 1*256/16000000 = 16us
ir_remote_xbox.pde
#include
#include
// This is the INT0 Pin of the ATMega8
#define PIN_IRRX 2
// Xbox IR remote control codes
#define IRCODE_DISPLAY 0x52AAD5
#define IRCODE_REVERSE 0x51DAE2
#define IRCODE_PLAY 0x515AEA
#define IRCODE_FORWARD 0x51CAE3
#define IRCODE_SKIPSUB 0x522ADD
#define IRCODE_STOP 0x51FAE0
#define IRCODE_PAUSE 0x519AE6
#define IRCODE_SKIPADD 0x520ADF
#define IRCODE_TITLE 0x51AAE5
#define IRCODE_INFO 0x53CAC3
#define IRCODE_MENU 0x508AF7
#define IRCODE_BACK 0x527AD8
#define IRCODE_UP 0x559AA6
#define IRCODE_DOWN 0x558AA7
#define IRCODE_LEFT 0x556AA9
#define IRCODE_RIGHT 0x557AA8
#define IRCODE_SELECT 0x5F4A0B
#define IRCODE_1 0x531ACE
#define IRCODE_2 0x532ACD
#define IRCODE_3 0x533ACC
#define IRCODE_4 0x534ACB
#define IRCODE_5 0x535ACA
#define IRCODE_6 0x536AC9
#define IRCODE_7 0x537AC8
#define IRCODE_8 0x538AC7
#define IRCODE_9 0x539AC6
#define IRCODE_0 0x530ACF
///////////////////////////////////////////////////////////////////////////////////////////////
volatile unsigned int usec;
volatile int ir_bit;
volatile unsigned long ir_tmp;
volatile unsigned long ir_cmd;
volatile boolean ir_cmd_new;
// Decoder state (variable "ir_bit") -1, 0, 1, 2, 3... 24 and (25)
#define WAIT -1 // Waiting for an IR signal
#define HEAD 0 // Receiving AGC pulse
//...
#define PERR 250 // Maximun absolute error permited in us
#define TIME_HEAD 4500 // AGC pulse lenght (in us)
#define TIME_BIT0 1500 // Bit 0 lenght (in us)
#define TIME_BIT1 2500 // Bit 1 lenght (in us)
// Aruino runs at 16 Mhz,
// Raise interrupt every 1 / ((16000000 / 1) / 256) = 1 / 62500 = 16us
///////////////////////////////////////////////////////////////////////////////////////////////
ISR(TIMER2_OVF_vect)
{
usec += 16;
if (usec == 6400) // 6400 % 16 == 0
{
// Time over! Reset receiver's state machine
usec = 0;
ir_bit = WAIT;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////
#define VALID_TIME(v, a, b) ( ((v) > (a)-PERR) && ((v) < (b)+PERR) )
// INT0, arduino pin2
ISR(INT0_vect)
{
switch (ir_bit)
{
case WAIT:
// Waiting for an incoming signal
if (!ir_cmd_new) ir_bit++;
break;
case HEAD:
// Initial AGC pulse
if ( VALID_TIME(usec, TIME_HEAD) )
{
ir_tmp = 0;
ir_bit++;
}
else
{
ir_bit = WAIT;
}
break;
default:
// Data bits 01, 02, 03, ... 24
if ( VALID_TIME(usec, TIME_BIT0) )
{
ir_tmp = ir_tmp << 1;
ir_bit++;
}
else if ( VALID_TIME(usec, TIME_BIT1) )
{
ir_tmp = (ir_tmp << 1) | 0x00000001;
ir_bit++;
}
else
{
ir_bit = WAIT;
}
if (ir_bit == 25)
{
// Bit "25" means END OF TRANSMISION, new IR data is available
ir_cmd = ir_tmp;
ir_cmd_new = true;
}
break;
}
usec = 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void setup()
{
usec = 0;
ir_bit = WAIT;
ir_cmd_new = false;
pinMode(PIN_IRRX, INPUT);
// Timer Setup, valid only for ATmega8
// Normal mode
TCCR2 &= ~(1 << WGM21);
TCCR2 &= ~(1 << WGM20);
// No Timer Prescaler
TCCR2 |= (1 << CS20);
TCCR2 &= ~(1 << CS22);
TCCR2 &= ~(1 << CS21);
// Use internal clock
ASSR &= ~(1 << AS2);
// TMR2 Overflow Interrupt
TIMSK |= (1 << TOIE2);
TIMSK &= ~(1 << OCIE2);
// INT0 interrupt
GICR |= (1 << INT0);
MCUCR |= (1 << ISC00); // positive edge
MCUCR |= (1 << ISC01); // negative edge
sei();
Serial.begin(9600);
// prints title with ending line break
Serial.println("");
Serial.println("Xbox IR remote control");
Serial.println("Waiting...");
// wait for the long strings to be sent to serial port
delay(100);
}
///////////////////////////////////////////////////////////////////////////////////////////////
void loop()
{
if (ir_cmd_new)
{
Serial.print("code - ");
Serial.println(ir_cmd, HEX);
ir_cmd_new = false;
delay(40);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
Mandos de otras marcas y modelos
Por desgracia, no hay ningún tipo de normalización y cada mando genera un código de línea (secuencia de pulsos y espacios) diferente. Puedes encontrar información sobre el formato de trama y códigos de distintos modelos en el proyecto [http://www.lirc.org/ LIRC (Linux Infrared Remote Control)].
f0 (kHz) | IC |
30 kHz | TFMS5300 |
33 kHz | TFMS5330 |
36 kHz | TFMS5360 |
38 kHz | TFMS5380 |
40 kHz | TFMS5400 |
56 kHz | TFMS5560 |
Si tienes un mando "rarito" que no aparece en LIRC tendrás que investigar tú mismo el código de línea que genera. Para ello puedes usar el programilla ir_remote_raw, que te comento a continuación. El hardware necesario para usarlo es el mismo, reemplazando el chip receptor por uno adecuado a la frecuencia de portadora del mando. Hay varios modelos del chip receptor de Telefunken (ver la tabla). Algunos mandos (muy pocos) usan incluso =455kHz. Si no sabes cual es la de tu mando usa un chip de =38kHz y seguro que aciertas. Aunque no uses el chip exacto lo único que ocurrirá es que disminuirá el alcance del mando. Prueba con un chip de 38kHz primero y si no funciona prueba con uno de 56kHz. Lo mejor es comprar un mando universal y configurarlo para que use el protocolo RC5 de Sony, que es el más popular. La especificación de dicho protocolo la tienes [http://www.atmel.com/dyn/resources/prod_documents/doc1473.pdf aquí] (con ejemplos en ensamblador para un micro AVR8)
ir_remote_raw
ir_remote_raw.pde
En este pequeño programa, la RSI de INT0 guarda en una tabla en memoria la duración de cada pulso y de cada espacio de la secuencia. Cuando la transmisión finaliza (lo decide un temporizador de inactividad al vencer), imprime por el puerto serie la lista de tiempos (''raw codes''). La lista presenta la duración de pulsos y espacios alternadamente (pulso-espacio-pulso-espacio-...) siendo el primer valor la duración del pulso inicial AGC.
Puedes imprimir esta lista en un fichero de texto desde HyperTerminal de Windows o Minicom en Linux. Los tiempos medidos no siempre son exactamente igualesPuedes probar a pulsar la tecla varias veces, cargar estos ficheros de datos en vectores en Matlab y promediar. Y eso para cada tecla del mando. Como ya habrás sospechado, el trabajo es de chinos... cada vez que pulsas la tecla ni tampoco el número de pulsos recibidos, procura pulsar la tecla del mando rápidamente (un golpe seco) para que envíe sólo una secuencia, prueba varias veces. Ajusta la constante TIME_OVER
si crees que el mando transmite más de una vez la misma secuencia en un corto espacio de tiempo. Puedes ver el cronograma de la señal usando la función [{{SERVER}}/{{DIR avr}}/irsignal.m irsignal.m] para Matlab.
A partir de esta lista de tiempos deberás fijarte bien e ''intentar'' inferir cómo se transmite el "1" y el "0". Se suelen usar 4 tipos de códigos de línea:
- Anchura de pulso. La distancia entre 1 flancos de subida y el siguiente de bajada (en realidad es al revés porque el TFMS5XX0 es activo a nivel bajo) determina si el bit es "0" o "1"
- Anchura de espacio. La distancia entre 1 flancos de bajada y el siguiente de subida (en realidad es al revés porque el TFMS5XX0 es activo a nivel bajo) determina si el bit es "0" o "1"
- Anchura de pulso o espacio. La distancia entre dos flancos consecutivos (de bajada o de subida) determina si el bit es "0" o "1". Este es precisamente el código de línea que usa el mando a distancia de la Xbox
- De la familia [http://es.wikipedia.org/wiki/Codificaci%C3%B3n_Manchester Manchester]. Un ejemplo de este código es el RC5 de Sony (bastante popular), para decodificarlo sólo es necesario que la rutina de servicio a la interrupción del temporizador muestree la señal en 1/4 y 3/4 del periodo de bit para saber si se trata de un "0" o "1"