IR Tinkering and Sony TV Remote

I have been trying on and off for a few months to simulate the ON/OFF button on a Sony Bravia TV remote, mainly as a vehicle to get up to speed with Arduino.

I finally got some success today, and now only need to bump the power through the IR LED (I think) via a transistor stage perhaps to improve the range.

I also built a PC file capture application for the Serial communication with the Arduino, allowing me to annotate the file on the run with comments etc. This is written in Delphi.

I feel that Arduino should have a supported microseconds() function, even if the fine accuracy is not perfect.

/*
 * IRTinker
 *
 * Tinkering with a typical TV remote control to determine its key
 * control sequences. My remote is a Sony RM-ED005 used with a Sony
 * Bravia TV.  
 * The H/W uses a Dick Smith Z-1955 IR Receiver chip, wired as follows.
 *   
 *          Z-1955        .---------.----- 47 ohm ------- +Vcc
 *           pin 3 -------|         |
 *                        |         |
 *                     2K2 ohm      |
 *                        |       47uF electro
 *           pin 1 -------.         |
 *                        |         |
 *                     1000pF       |  
 *                        |         |
 *           pin 2 -------|         |
 *                        .---------.------------------- Gnd
 *
 * The IR chip output pin 1 connects to Arduino digital input pin 7
 * Vcc (5V) and Gnd are obtained from the Arduino.
 *
 * Other receiver chips are detailed in LIRC documentation.
 * See www.lirc.org/receivers.html
 *
 * The S/W essentially sits on the IR receiver pin, watching for 
 * changes in state, and recording the number of times it was detected
 * in the previous state. We can then use overall time differences
 * to determine actual times corresponding to each count.
 *
 * Raw results are written to serial output for analysis.
 * Because things are happening at the 10 microsecond level, we can't 
 * afford to place delays or serial prints within the detection loop.
 * 
 * Background LIRC Information
 *
 * Typical raw microsecond timings for this remote in the LIRC 
 * literature are as follows:- (TVBlue button example)
 * 2304  600 1152  576 1152  576 1152  576  576  600 1152  576 1152  576
 *  576  576  576  600  576  600 1152  576  576  576  576  576  576
 *  576  576  576  576 20232, with 2 more repeats of this whole sequence.
 * The numbers alternate ON/OFF times over a basic 38 kc/s carrier.
 * The first few and the last few components of this sequence envelope 
 * the data for the particular button pressed.  
 *
 * Processed Hex button LIRC formats are typically specified like
 *  <code name="TVChannel1" data="b5f"/>
 *  <code name="TVChannel2" data="80b5f"/>
 *  <code name="TVChannel3" data="40b5f"/>
 *  <code name="TVChannel4" data="c0b5f"/>
 *  <code name="TVChannel5" data="20b5f"/>
 *  <code name="TVChannel6" data="a0b5f"/>
 *  <code name="TVChannel7" data="60b5f"/>
 *  <code name="TVChannel8" data="e0b5f"/>
 *  <code name="TVChannel9" data="10b5f"/>
 *  <code name="TVChannel0" data="90b5f"/>
 *  <code name="TVEnter" data="b0b5f"/>
 *  <code name="TVUp" data="9eb5f"/>
 *  <code name="TVDown" data="5eb5f"/>
 *  <code name="TVLeft" data="deb5f"/>
 *  <code name="TVRight" data="3eb5f"/>
 *  <code name="TVPicture" data="2ab5f"/>
 *  <code name="TVVolumeUp" data="8cb5f"/>
 *  <code name="TVVolumeMute" data="4cb5f"/>
 *  <code name="TVChannelDown" data="1cb5f"/>
 *  <code name="TVChannelUp" data="c8b5f"/>
 *  <code name="TVVolumeDown" data="28b5f"/>
 *  <code name="TVChannelMode" data="48b5f"/> 
 *
 * Results
 * I experimented with the TVBlue, and Channel 1 and 2 and On/Off buttons.
 * I have reformatted the results from the raw program output to the
 * serial port to improve the readability.
 *
 * Seq   TVBlue    TVBlue    On/Off     Channel1    Channel2 
 *       (count)   (usec)    (count)     (count)     (count)
 *  0        0         0         0           0           0
 *  1     -412      2801      -409        -410        -419
 *  2       61       414        63          62          53
 *  3     -221  0   1502      -220   0    -126   1    -227  0
 *  4       65       442        67          65          60
 *  5     -218  0   1482      -122   1    -124   1    -127  1
 *  6       66       448        66          64          61  
 *  7     -216  0   1468      -216   0    -127   1    -125  1
 *  8       68       462        67          61          63
 *  9     -218  0   1482      -122   1    -126   1    -126  1 
 * 10       68       462        66          62          61
 * 11     -119  1    809      -215   0    -126   1    -126  1
 * 12       69       469        68          61          62
 * 13     -121  1    822      -120   1    -127   1    -126  1
 * 14       66       448        68          61          62
 * 15     -215  0   1462      -123   1    -126   1    -125  1
 * 16       68       462        64          62          63
 * 17     -214  0   1455      -214   0    -214   0    -216  0
 * 18       70       476        70          69          67
 * 19     -212  0   1441      -118   1    -120   1    -119  1
 * 20       71       482        69          68          69
 * 21     -118  1    802      -120   1    -119   1    -119  1
 * 22       70       476        68          69          68
 * 23     -117  1    795      -120   1    -120   1    -120  1
 * 24       70       476        68          67          68
 * 25     -118  1    802      -120   1    -121   1    -119  1
 * 26     3739     25425      3990        4267        4199 
 *    
 *    Sequences repeat twice more for each button 27-52 53-78
 *    - indicates pin LOW 
 *    At seq 1 a longer 'alert' pulse occurs
 *    At seq 26 a  very long 'end of sequence' pulse occurs
 *    The even sequences are otherwise HIGH
 *    The odd sequences with counts around 120 are likely '1' bits
 *    The odd sequences with counts around 220 are likely '0' bits
 *
 *    Summarising
 *       TVBlue 000011000111 = hex '0c7'
 *       On/Off 010101101111 = hex '56f'
 *       Ch 1   111111101111 = hex 'fef'
 *       Ch 2   011111101111 = hex '7ef'
 * 
 *    There is not much detailed agreement with the LIRC
 *    information except in the overall structure, but the
 *    Arduino platform turned this into a very feasible
 *    project.
 *
 *    If there is not already, a time counter finer than 
 *    'millis()' would have been useful. 
 */
/*    I also experimented in the other direction, trying to actually
 *    turn the TV on etc. The loop function is amended to call 'decode'
 *    to analyze the Remote, or to call IRSender() to send an ON/OFF
 *    sequence to the TV.
 *
 *    I used some code from the Forum/Playground to set up a 38400 Hz
 *    carrier, and turned it on off at pin 11. I am still at a loss
 *    as to whether the IR transmitting diode transmits natively at
 *    this frequency, or whether you need to supply the carrier at all.
 *    
 *    This code successfully turns the TV on/off, using pin 11 to 220
 *    ohmm to IR LED to ground, although the diode needs to be within 
 *    150 mm of the TV sensor. I will try a transistor amplification 
 *    stage.
 *
 *    I tuned the pulse and mark times using one Arduino running the
 *    detector stage, communicating with either the TV remote or a 
 *    second Arduino running the simulated pulse sequence.
 *
 *    I also built a Serial Capture program in Delphi which lets me 
 *    nominate a capture file, and annotate it with running comments etc. 
 */
extern volatile unsigned long timer0_millis; //timer0_millis is defined in wiring.c

#ifndef cbi // Definitions for setting and clearing register bits
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define SYSCLOCK 16000000  // main system clock of Arduino Board (Hz)

#define IROUT 11    // pin 11 is OC2A output from TIMER2
#define BURST_FREQUENCY 38400 //IR Burst Frequency in Hz

unsigned long time = 0;

unsigned long microseconds();
void setup_timer2(unsigned int freq);
uint8_t timer2top(unsigned int freq) ;
 
int ledXmt = 11;                // IR transmitter diode to digital pin 3;
int ledPin = 13;                // LED connected to digital pin 13
int ledIRR = 7;                 // IR receiver to digital pin 7
int readVal;                    // IR receiver read value (state)
int usec;                       // calibrate result (usec per digitalRead)
int Event[200];                 // count of successive IR readings 
int lastseen;                   // last state seen

void pulser(unsigned int usecsON, unsigned int usecsOFF)
{ TCNT0 = 0;
  time = microseconds();
  sbi(TCCR2A,COM2A0) ;   // connect pulse clock
  time = time + usecsON;
  while(microseconds()<time){};
  cbi(TCCR2A,COM2A0) ;   // disconnect pulse clock
  time = time + usecsOFF;
  while(microseconds()<time){};
}

// return TIMER2 TOP value per given desired frequency (Hz)
uint8_t timer2top(unsigned int freq)
{  return((byte)((unsigned long)SYSCLOCK/2/freq) - 1) ;
}

void setup_timer2(unsigned int freq)
{ cbi(TCCR2A,COM2A1); // disconnect OC2A for now (COM2A0 = 0)
  cbi(TCCR2A,COM2A0);
  cbi(TCCR2B,WGM22);  // CTC mode for TIMER2
  sbi(TCCR2A,WGM21);
  cbi(TCCR2A,WGM20);
  TCNT2 = 0;
  cbi(ASSR,AS2);  // use sys

Here is the code bit, not truncated.

extern volatile unsigned long timer0_millis; //timer0_millis is defined in wiring.c

#ifndef cbi // Definitions for setting and clearing register bits
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define SYSCLOCK 16000000  // main system clock of Arduino Board (Hz)

#define IROUT 11    // pin 11 is OC2A output from TIMER2
#define BURST_FREQUENCY 38400 //IR Burst Frequency in Hz

unsigned long time = 0;

unsigned long microseconds();
void setup_timer2(unsigned int freq);
uint8_t timer2top(unsigned int freq) ;
 
int ledXmt = 11;                // IR transmitter diode to digital pin 3;
int ledPin = 13;                // LED connected to digital pin 13
int ledIRR = 7;                 // IR receiver to digital pin 7
int readVal;                    // IR receiver read value (state)
int usec;                       // calibrate result (usec per digitalRead)
int Event[200];                 // count of successive IR readings 
int lastseen;                   // last state seen

void pulser(unsigned int usecsON, unsigned int usecsOFF)
{ TCNT0 = 0;
  time = microseconds();
  sbi(TCCR2A,COM2A0) ;   // connect pulse clock
  time = time + usecsON;
  while(microseconds()<time){};
  cbi(TCCR2A,COM2A0) ;   // disconnect pulse clock
  time = time + usecsOFF;
  while(microseconds()<time){};
}

// return TIMER2 TOP value per given desired frequency (Hz)
uint8_t timer2top(unsigned int freq)
{  return((byte)((unsigned long)SYSCLOCK/2/freq) - 1) ;
}

void setup_timer2(unsigned int freq)
{ cbi(TCCR2A,COM2A1); // disconnect OC2A for now (COM2A0 = 0)
  cbi(TCCR2A,COM2A0);
  cbi(TCCR2B,WGM22);  // CTC mode for TIMER2
  sbi(TCCR2A,WGM21);
  cbi(TCCR2A,WGM20);
  TCNT2 = 0;
  cbi(ASSR,AS2);  // use system clock for timer 2
  OCR2A = timer2top(freq);
  cbi(TCCR2B,CS22);  // TIMER2 prescale = 1
  cbi(TCCR2B,CS21);
  sbi(TCCR2B,CS20);
  cbi(TCCR2B,FOC2A);  // clear forced output compare bits
  cbi(TCCR2B,FOC2B);
}

unsigned long microseconds()
{ return (timer0_millis * 1000 + 4*TCNT0);
}
 
void setup()                    // run once, when the sketch starts
{
  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
  pinMode(ledIRR, INPUT);       // IR receiver
  pinMode(ledXmt, OUTPUT);      // IR transmitter
  pinMode(IROUT, OUTPUT) ;

  setup_timer2(BURST_FREQUENCY);

  Serial.begin(9600);
}

void detector()                     // run over and over again
{ int j,k,li;
  int ndx,tusec;
  long l,loops;
  int i = 0;
  int done = 0;
  long ton,toff;
  ndx = 0;                        // clear array index
  lastseen = HIGH;
  loops = 0;
  k = 0;
  for (j = 0; j < 200; j++)   
     {  Event[j] = 0;             // clear count array
     }
  ton = millis();   
  while (done==0)
     { readVal = digitalRead(ledIRR);
       loops++;                   // count total times through loop
       i++;
       if (i > 32000)             // if it goes quiet we are done
          { i = 0;
            k++;
            if (k > 5) done = 1;  // double loop covers int overflows
          }  
       if (readVal != lastseen)   // has state changed
          { if (ndx < 199) ndx++;
            lastseen = readVal;
          }
       if (ndx > 0) Event[ndx]++;
     }
  toff = millis();
  l = 10000*(toff - ton);
  tusec = l/loops;           // tenthmicrosec per loop 
  Serial.println(l);
  Serial.println(tusec);
  Serial.println(loops);  
  Serial.println('<'); 
  i = HIGH;   
  for (int j = 0; j < ndx; j++)   
     {  Serial.print(j);
        Serial.print("  ");
        if (i == HIGH)             // + if High, - Low
           { Serial.print('+');
           }   
          else 
           { Serial.print('-');
           }
        Serial.print(Event[j]);    // raw count 
        Serial.print("  ");   
        l = Event[j];
        li = l * tusec / 10;
        Serial.println(li);        // convert to microsecs 
        i = HIGH - i;              // flip state (display) 
     }
  Serial.println('>');             // end of trace
}

void detector2()                     // run over and over again
{ int j;
  int ndx,waitfor;
  long l,ptim;
  long Eventl[200];
  int i = 0;
  int done = 0;
  ndx = 0;                        // clear array index
  waitfor = HIGH;
  for (j = 0; j < 200; j++)   
   {  Eventl[j] = 0L;            // clear count array
   }
  while (done==0)
   {  ptim = pulseIn(ledIRR,waitfor,15000L);
      if (ptim == 0L)
        { done = 1; 
        }
       else  
        { waitfor = HIGH - waitfor;
          Eventl[ndx] = ptim;
          if (ndx < 199) ndx++;
        }  
   }  
  Serial.println('<'); 
  i = HIGH;   
  for (int j = 0; j < ndx; j++)   
     {  Serial.print(j);
        Serial.print("  ");
        if (i == HIGH)             // + if High, - Low
           { Serial.print('+');
           }   
          else 
           { Serial.print('-');
           }
        Serial.println(Eventl[j]);    // raw count 
        i = HIGH - i;              // flip state (display) 
     }
  Serial.println('>');             // end of trace
}

void delayMine(int usec)
{  delayMicroseconds(usec);
}
 
void issue(unsigned int j)
{  int k,bit;
   int msk[] = {8,4,2,1};
   for (k = 0; k < 4; k++)  
     { bit = j & msk[k];
       if (bit == 0) pulser(1180,560); else pulser(600,560);
     }
} 

void RemoteButton( char Remote[], char Button[])
// Remote is say SonyXXX
// Button is say a0e5
{  int i,j,k,done;
   char kar;
   i = 0;
   done = false;
   pulser(2240,576);               // preamble
   while (!done)
   {  kar = Button[i];
      i++;
      j = (int)kar;
      if (j == 0) 
         { done = true;
           break;
         }  
      if (kar >= '0' && kar <= '9')
         { k = j - 48;
           issue(k);
         }
        else if (kar >= 'a' && kar <= 'f')
         { k = j - 55;
           issue(k);
         }
        else if (kar >= 'A' && kar <= 'F')
         {
         }
   }   
   pulser(0,24000);                   // postamble
}

void IRSender()
{ int j;
  Serial.print("(");
  digitalWrite(ledPin,HIGH);
  for (j = 0; j < 3; j++)  
   { 
      RemoteButton("Sony","56f");
   }
  Serial.println(")");  
  digitalWrite(ledPin,LOW);
  delay(15000);
}

void loop()
{ //detector();
  IRSender();
}

Very cool :slight_smile:

Thanks for sharing :slight_smile: