[UPDATE 2009-01-17]
This article is still valid, but I recommend checking out Arduino IR remote control – more advanced, which has a detailed explanation of the wiring and a zip file with some updated code.
[/UPDATE]

For the 2008 Winter Solstice, my wife got me an Arduino Duemilanove, and it rules! This little board is a ton of fun to play with, and although I don’t know a great deal about electronics, I am about to learn. For example, I’ve created this cool infrared receiver. It does work, and I’m publishing my results so that someone can help me debug the circuit.

My goal was to use an old Sony RMT-V202 remote control, then do something both on the Arduino board and on my computer. So, there’s a red LED that will light up when you press the power button on the remote, and the serial monitor now says “Power” when you push the power button. This is a proof of concept, more than anything else, since there are tons of buttons on the remote, and I can now do stuff with all of them.

For example, if you press the “display” button on my remote, you can see the ID of the keys you press. If you press the “record” button, you can see the length (in milliseconds) of the IR pulse that actually encodes the button you pressed. These key-mappings are handled in software, and can be reconfigured without rewiring the circuit at all. Using these two buttons, I figured out the encoding for the “power” button, and then mapped that onto the red LED.

I started with some code written by pmalmsten, which I found in the Arduino forum. I’ve heavily modified the original code, such that it is hardly like the version on the forum.

// 0.1 by pmalmsten
// 0.2 by farkinga

#define IR_BIT_LENGTH 12      
#define BIT_1 1000          //Binary 1 threshold (Microseconds)
#define BIT_0 400           //Binary 0 threshold (Microseconds)
#define BIT_START 2000      //Start bit threshold (Microseconds)
#define DEBUG 0             //Serial connection must be started to debug
#define IR_PIN 7            //Sensor pin 1 wired through a 220 ohm resistor
#define LED_PIN 9           //"Ready to Recieve" flag, not needed but nice
#define POWER_PIN 11     // the red LED that indicates if the power button is pressed.

int runtime_debug = 0;
int output_key = 0;
int power_button = 0;

void setup() {
  pinMode(LED_PIN, OUTPUT);		//This shows when we're ready to recieve
  pinMode(POWER_PIN, OUTPUT);		//This is the "power on" indicator
  pinMode(IR_PIN, INPUT);
  digitalWrite(LED_PIN, LOW);	    //not ready yet
  Serial.begin(9600);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);	   //Ok, i'm ready to recieve

  int key = get_ir_key();		    //Fetch the key

  digitalWrite(LED_PIN, LOW);

  do_response(key);

  delay(200);
}

void do_response(int key)
{  
  switch (key)
  {
    case 1437:
      Serial.println("toggle debug pulse");
      runtime_debug = 1 - runtime_debug;
      break;
    case 1498:
      Serial.println("Toggle key output");
      output_key = 1 - output_key;
      break;
    case 1429:
      Serial.println("Power");
      power_button = 1 - power_button;
      if (power_button)
      {
        digitalWrite(POWER_PIN, HIGH);
      }
      else
      {
        digitalWrite(POWER_PIN, LOW);
      }
      break;
    case 1424:
      Serial.println("Channel Up");
      break;      
    case 1425:
      Serial.println("Channel Down");
      break;
    default:
      if (output_key)
      {
        Serial.print("Key ");
        Serial.print(key);
        Serial.println(" not programmed");
      }
      break;
  }
}

void read_pulse(int data[], int num_bits)
{
  for (int i = 0; i < num_bits; i++)
  {
    data[i] = pulseIn(IR_PIN, LOW);
  }
}

void pulse_to_bits(int pulse[], int bits[], int num_bits)
{
  if (DEBUG || runtime_debug) { Serial.println("-----"); }
  
  for(int i = 0; i < num_bits ; i++) 
  {
    if (DEBUG || runtime_debug) { Serial.println(pulse[i]); }
    
    if(pulse[i] > BIT_1) //is it a 1?
    {
      bits[i] = 1;
    }  
    else if(pulse[i] > BIT_0) //is it a 0?
    {
      bits[i] = 0;
    } 

    else //data is invalid...
    {
      Serial.println("Error");
    }
  }
}

int bits_to_int(int bits[], int num_bits)
{
  int result = 0;
  int seed = 1;
  
  //Convert bits to integer
  for(int i = 0 ; i < num_bits ; i++) 
  {		  
    if(bits[i] == 1) 
    {
	result += seed;
    }
    
    seed *= 2;
  }
  
  return result;
}

int get_ir_key() 
{
  int pulse[IR_BIT_LENGTH];
  int bits[IR_BIT_LENGTH];  

  do {} //Wait for a start bit
  while(pulseIn(IR_PIN, LOW) < BIT_START);

  read_pulse(pulse, IR_BIT_LENGTH);
  
  pulse_to_bits(pulse, bits, IR_BIT_LENGTH);

  return bits_to_int(bits, IR_BIT_LENGTH);
}

Several runtime variables have become compiler constants, and large chunks of code have been modularized in what I consider to be a "sane manner." Here's the bottom line: when you press a button on the remote, you want the computer to receive a unique integer that corresponds to the button you pushed. The code above will accomplish this goal. Once you know what button you've pressed, then you can use simply logic to do different things when different keys are pressed.

  • Aaron

    That looks like C, what's the language you're programming in again?

  • farkinga

    It's basically java, but I don't actually understand which subset I'm restricted to for the purposes of targeting the Arduino. Right now, I'm sticking to a subset that happens to overlap with C.

  • farkinga

    I take it back – this is totally c++, with some c libraries thrown in. It seems the IDE is written in java? I'm not sure, entirely.

  • Aaron

    regardless, cool shit! Keep us posted on the developments :)

  • Pingback: rtfa.net » Arduino IR remote control - more advanced

  • sinaptik

    How did you wire the pins to the IR sensor? I found a 3 pin sensor laying around and have 5v, gnd and a pin going to analog input. The arduino reads about 806 when no signal is sent and about 795 when there is an IR signal. It's unsuitable for your code a it can't make an input go high and low, and pulseIn will not work.

    Any suggestions?

  • farkinga

    Interesting… I was originally using an analog pin on the Arduino,
    but I had problems with the timing resolution… In pseudocode, it
    looked like this:

    loop {
    record current time in miliseconds
    read current voltage from analog input (which is quantized to a value
    between 0-1023)
    compare current voltage to previous reading of voltage
    if voltage has switched from low to high, save current time as “start
    of pulse”
    else if voltage has switched from high to low, determine pulse length
    by subtracting “start of pulse” from current time
    }

    This sortof works, but I think the number of operations in the loop
    take too many clock cycles to get an accurate measure of the number
    of milliseconds that have elapsed.

    So, I have two recommendations:

    1) have you checked out my updated video? It is <a href=”http://
    http://www.rtfa.net/2009/01/02/arduino-ir-remote-contr...
    advanced”>http://www.rtfa.net/2009/01/02/arduino-ir-remote-control-
    more-advanced The “more advanced” video describes the wiring in
    greater detail… Also, I have a zip file with newer code.

    2) do what you can to use a digital input pin, because pulseIn has
    much higher timing resolution than the analog hack I described
    above. I do have my old analog code, if you want to see it, but I
    got farther by switching from analog to digital. On my IR sensor, I
    use 5v in, then 220 ohms resistance between the pinout and my
    arduino's digital input. I'd venture that you just need to calculate
    the right resistance for your IR sensor to get that lower value close
    to 0. When I have 220 ohms resistance on the IR sensor's pinout, my
    analog readings were more like 5 and 300, so it was ready to plug
    into a digital pin.

    Please let me know how this works for you. Also, I'm going to put a
    note in this article to refer people to the new video. Thanks for
    trying this out!

  • farkinga

    er, I need to fix that link…

  • farkinga
  • sinaptik

    I swapped the 5v and arduino in pins on the IR reciever and now I get 1023 when there is no signal and 6 when there is a signal so it works with digital now! Your code only works with pulse-coded remotes.

    I have a philips remote and I found that it's shift coded (manchester encoding) and I wrote code to decode it, but gave up after I could not find how many bits were sent (I got to 32).

    You're quite lucky to find a pulse-coded remote. This site (http://scv.bu.edu/GC/shammi/ir/) may help you further your project, it contains explanations of the other types of bit coding. Space coding seems to be impossible…

  • sinaptik

    Scratch that, space coding is possible. I have a JVC unit in my car that uses distance encoding. Will try code that up in the morning.

  • sinaptik

    Done, it reads the address and the command sent by my JVC remote control, it's still a bit messy:

    #define L 7

    int i = 0;

    void setup()
    {
    Serial.begin(9600);
    pinMode(7, INPUT);
    }

    void loop()
    {
    int temp = getKey();
    delay(100);
    }

    int getKey()
    {
    int rawadd[L];
    int rawcom[L];
    int bitadd[L];
    int bitcom[L];

    do
    {
    }
    while(pulseIn(7, LOW) < 8300); //Wait for header

    readAdd(rawadd, L);
    readCom(rawcom, L);

    pulseTobits(rawadd, bitadd, L);
    pulseTobits(rawcom, bitcom, L);

    Serial.println(bitsToint(bitadd, L));
    Serial.println(bitsToint(bitcom, L));
    Serial.println();
    return 0;
    }

    void readAdd(int pulse[], int length)
    {
    for(i = 0; i <= length; i++)
    {
    while(digitalRead(7) != LOW) //Wait to go to right state
    {
    }

    pulse[i] = (int)pulseIn(7, HIGH);
    }
    }

    void readCom(int pulse[], int length)
    {
    for(i = 0; i <= length; i++)
    {
    while(digitalRead(7) != LOW) //Wait to go to right state
    {
    }

    pulse[i] = (int)pulseIn(7, HIGH);
    }
    }

    void pulseTobits(int pulse[], int bits[], int length)
    {
    for(i = 0; i <= length; i++)
    {
    if((pulse[i] < 500) && (pulse[i] > 200))
    {
    bits[i] = 0;
    }
    else if((pulse[i] > 1200) && (pulse[i] < 1500))
    {
    bits[i] = 1;
    }
    }
    }

    int bitsToint(int bits[], int length)
    {
    int result = 0;
    int seed = 1;

    for(i = length; i >= 0; i–) //LSB is first
    {
    if(bits[i] == 1)
    {
    result += seed;
    }

    seed *= 2;
    }

    return result;
    }

  • farkinga

    sinaptik: you rule. I'm glad you found some workable hardware, and
    I'm going to make a note that my code is appropriate for pulse coded
    remotes. Thanks for the link, because I was thinking it would be
    cool to eventually make an arduino tv-b-gone, and the different
    coding types will be critical.

  • farkinga

    …is space coding the same as distance coding?

  • sinaptik

    Space coding is the same as distance coding. I found another site that has a lot of useful information about the different protocols (http://www.sbprojects.com/knowledge/ir/jvc.htm), and LIRC (http://www.lirc.org/) which has the codes for a lot of remotes, for example I was using http://lirc.sourceforge.net/remotes/jvc/RM-RK50 for my remote.

    I'm sure you could find a lot of 'power' codes from that site and make a tv-b-gone.

  • sinaptik

    Space coding is the same as distance coding. I found another site that has a lot of useful information about the different protocols (http://www.sbprojects.com/knowledge/ir/jvc.htm), and LIRC (http://www.lirc.org/) which has the codes for a lot of remotes, for example I was using http://lirc.sourceforge.net/remotes/jvc/RM-RK50 for my remote.

    I'm sure you could find a lot of 'power' codes from that site and make a tv-b-gone.