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

blog comments powered by Disqus