Go Down

Topic: Event-driven implementation of the NEC IR remote control protocol (Read 843 times) previous topic - next topic

Alexandre Syenchuk

Hello all,

I was trying to control my robot using a remote, and I didn't found anything appropriate. There is the IRRemote library out there, but:
- it is a little bit heavy (cpu, sram and flash usage)
- I had troubles using time-related functions at the same time
- even if it claims to use interrupts internally, the readings are blocking
- it doesn't really implement the NEC protocol, since the bits order is reversed and it returns a 4 bytes value with redundancy instead of a 2 bytes value (and it doesn't check if the redundant values match the original ones)
- it doesn't repeat the previous value but sends a "special" repeat value when you keep pushing a button on your remote

Still, credits to the author for inspiration!

I tried to make something efficient. I think I'm doing quite well with memory, only 8 bytes for internal processing (I think it could be reduced to 5, but I need more time to think about it), and 2 bytes for the returned value. It's pretty reactive to, because it calculates the code while the signal is still received, and does so in a short interrupt sequence. Which means that you can still run some other code while the IR decoder is receiving and decoding the signal, and your callback function is executed as soon as the signal is received (when it detects the end of the signal, the result is already computed).

And I don't claim to provide better here, but I may work on it later to provide something more ready-to-use in your own project. Meanwhile, here is some code:

Code: [Select]
#define IR_PIN 2
#define IR_TOLERANCE 15 // error tolerance (percentage)
#define IR_OFFSET 50 // IR receiver offset, in ┬Ás (+offset for marks and -offset for spaces)
#define IR_LPM 9000 // leading pulse mark
#define IR_LPS 4500 // leading pulse space
#define IR_STDM 563 // standard mark
#define IR_STDS 563 // standard space
#define IR_LNGS 1688 // long space
#define IR_RPS 2250 // repeat code space
#define IR_INTS 40000 // minimal space between two codes

volatile uint32_t ir_time = 0;
volatile uint8_t ir_counter = 0;
volatile uint8_t ir_addr = 0x00;
volatile uint8_t ir_cmd = 0x00;
volatile bool ir_valid = false;
void (*ir_cb)(uint8_t, uint8_t);

void IR_read()
{
 // calculates time offset for this reading
 uint32_t cur_time = micros();
 uint32_t diff_time = cur_time - ir_time;
 ir_time = cur_time;
 
 // sets true if it is a raising signal detection
 bool raising = !digitalRead(IR_PIN);
 
 // adjusts timing offsets to compensate IR receiver lag
 if (raising) {
   diff_time += IR_OFFSET;
 }
 else {
   diff_time -= IR_OFFSET;
 }
 
 // ignores falling signal if no reading is in process
 if (!ir_counter && !raising) {
   return;
 }
 
 // starts reading a signal
 if (!ir_counter && raising && IR_gt(diff_time, IR_INTS)) {
   ir_counter = 1;
   return;
 }
 
 // ignores signal if reading didn't start properly
 if (!ir_counter) {
   return;
 }
 
 if (!raising) {
   // ignores noise
   if (ir_counter % 2 == 0) {
     return;
   }
   
   // leading pulse mark
   if (ir_counter == 1) {
     if (IR_et(diff_time, IR_LPM)) {
       ir_counter++;
     }
     else {
       ir_counter = 0;
     }
     return;
   }
   
   // bit mark
   if (ir_counter <= 65) {
     if (IR_et(diff_time, IR_STDM)) {
       ir_counter++;
     }
     else {
       ir_counter = 0;
     }
     return;
   }
   
   // end mark
   if (ir_counter == 67) {
     if (IR_et(diff_time, IR_STDM)) {
       ir_valid = true;
       (*ir_cb)(ir_addr, ir_cmd);
     }
     ir_counter = 0;
   }
 }
 else {
   // ignores noise
   if (ir_counter % 2 == 1) {
     return;
   }
   
   // leading pulse space
   if (ir_counter == 2) {
     if (IR_et(diff_time, IR_LPS)) {
       ir_addr = 0x00;
       ir_cmd = 0x00;
       ir_valid = false;
       ir_counter++;
     }
     else if (IR_et(diff_time, IR_RPS) && ir_valid) {
       ir_counter = 67;
     }
     else {
       ir_counter = 0;
     }
     return;
   }
   
   // bit space
   if (ir_counter <= 66) {
     if (IR_et(diff_time, IR_STDS)) {
       if (ir_counter <= 18) {
         ir_addr >>= 1;
       }
       else if (ir_counter <= 34) {
         if (!((1 << (ir_counter - 20) / 2) & ir_addr)) {
           ir_counter = 0;
           return;
         }
       }
       else if (ir_counter <= 50) {
         ir_cmd >>= 1;
       }
       else {
         if (!((1 << (ir_counter - 52) / 2) & ir_cmd)) {
           ir_counter = 0;
           return;
         }
       }
       ir_counter++;
     }
     else if (IR_et(diff_time, IR_LNGS)) {
       if (ir_counter <= 18) {
         ir_addr >>= 1;
         ir_addr |= 0x80;
       }
       else if (ir_counter <= 34) {
         if ((1 << (ir_counter - 20) / 2) & ir_addr) {
           ir_counter = 0;
           return;
         }
       }
       else if (ir_counter <= 50) {
         ir_cmd >>= 1;
         ir_cmd |= 0x80;
       }
       else {
         if ((1 << (ir_counter - 52) / 2) & ir_cmd) {
           ir_counter = 0;
           return;
         }
       }
       ir_counter++;
     }
     else {
       ir_counter = 0;
     }
     return;
   }
 }
}

bool IR_lt(uint32_t v_in, uint32_t v_ref)
{
 return (v_in <= v_ref) || (v_in - v_in * IR_TOLERANCE / 100 <= v_ref);
}

bool IR_gt(uint32_t v_in, uint32_t v_ref)
{
 return (v_in >= v_ref) || (v_in + v_in * IR_TOLERANCE / 100 >= v_ref);
}

bool IR_et(uint32_t v_in, uint32_t v_ref)
{
 return IR_gt(v_in, v_ref) && IR_lt(v_in, v_ref);
}


You need to connect the IR sensor pin to pin 2. Here is an example:

Code: [Select]
void IR_process(uint8_t addr, uint8_t cmd)
{
 Serial.print("IR BURST: ");
 Serial.print(addr, HEX); Serial.print(" "); Serial.print(cmd, HEX);
 Serial.println();
}

void setup()
{
 Serial.begin(9600);
 pinMode(IR_PIN, INPUT_PULLUP);
 digitalWrite(IR_PIN, HIGH);
 pinMode(13, OUTPUT);
 digitalWrite(13, LOW);
 
 attachInterrupt(0, IR_read, CHANGE);
 ir_cb = &IR_process;
}

void loop()
{
 digitalWrite(13, ir_counter);
}


Cheers,

Alexander

Go Up