Introduction: I2C InfraRed Remote Control With the Arduino

This Instructable details how to create a universal remote controller using I2C for the interface.

How odd you say, using an I2C slave device?

Yes, an I2C slave device.

This is because the accurate timing of IR packets is quite demanding and one a typical Arduino will struggle with if it is already carrying out many other tasks at the same time. It is better to distribute the computing load by assigning timing intensive activities to dedicated processors whenever possible (better still do it in hardware). Given I2C is a well documented and robust communications method between ICs, I chose this as the interface.


As mentioned above this instructable describes how to control domestic appliances such as TV, DVD player and Satellite etc. using the IRremote library on the Arduino.

It concludes with a design example turning the Arduino into an I2C slave remote control module (pic 1 above) with prototype test circuit (pic 2 above) and goes on to detail how to shrink your design down to the minimum components necessary so it can be embedded into another design. In my case I use this embedded device in an IoT Universal remote control device based around an ESP8266-12E.


What parts do I need?

To build the circuit depicted in Step 1 (IR Transmitter) you will need the following parts;

  • 2 off 10K resistors
  • 1 off 390R resistor
  • 1 off 33R resistor
  • 1 off 3K8 resistor
  • 1 off Red LED
  • 1 off IR Led TSAL6400
  • 1 off Transistor BC337
  • 1 off 220uF capacitor
  • 1 off Arduino Uno

To build the circuit depicted in Step 4 (IR Receiver) you will need the following parts;

  • 1 off 10K resistor
  • 1 off TSOP38328
  • 1 off 220uF capacitor
  • 1 off Arduino Uno

To build the circuit depicted in Step 5 (Slave test circuit) you will need the following parts;

  • 4 off 10K resistors
  • 2 off 390R resistor
  • 1 off 33R resistor
  • 1 off 3K8 resistor
  • 2 off Red LED
  • 1 off IR Led TSAL6400
  • 1 off Transistor BC337
  • 1 off 220uF capacitor
  • 2 off SPST Buttons
  • 2 off Arduino Unos

To build the circuit depicted in Step 6 (Shrunk design) you will need the following parts;

  • 3 off 10K resistors
  • 1 off 270R resistor
  • 1 off 15R resistor
  • 4 off 1K resistors
  • 1 off Red LED
  • 1 off IR Led TSAL6400 or TSAL5300
  • 1 off Transistor BC337
  • 1 off 220uF capacitor electrolytic @ 6.3v
  • 1 off 1000uF capacitor electrolytic @ 6.3v
  • 2 off 0.1uF capacitors
  • 2 off 22pF capacitors
  • 1 off 16MHz Xtal
  • 1 off ATMega328P-PU

Note : You will also require an FTDI device to programme the ATMega328P


What skills do I need?

  • A minimal grasp of electronics,
  • Knowledge of Arduino and it's IDE,
  • A little patience,
  • Some understanding of I2C would be useful (see here for some generic I2C/Wire Library details).


Topics covered

    • Brief overview of the circuit,
    • Brief overview of the software,
    • I2C Packet content,
    • Acquiring remote control codes (ui32Data),
    • How to test your I2C Slave device,
    • Shrinking your design,
    • Conclusion,
    • References used.



    As always, you use these instructions at your own risk and they come unsupported.

    Step 1: Brief Overview of the Circuit

    The purpose of the circuit is to transmit IR remote control codes. It's design is pretty straight forward and quite simple.

    When transistor Q1 a BC337 NPN is turned on via a logic one from Arduino PWM O/P D3 to Resistor R5, current passes through Leds 1 and 2. Limited only by ballast resistors R3 and R4 respectively. Q1 is used to boost the current passing through the IR Diode (IF Max = 100mA) to that in excess of what the Arduino O/P is capable ~40mA @ +5v supply.

    Capacitor C1 a 220uF Electrolytic provides some stabilisation preventing a supply rail drop by the power drawn by Leds 1 and 2.

    Resistors R1 and R2 are I2C pull ups.

    Step 2: Brief Overview of the Software


    To successfully compile this source code you will need the following extra library;


    Code Overview

    As shown in picture 1 above, on start up the code configures the micro-controller I/O then polls the status of the internal software flag 'bFreshDataFlag'. When this flag is set the controller asserts it's 'Busy' line (sending data pin D4 low) and moves to the 'eBUSY' state sequentially reading button press commands held in uDataArray[] and sending the IR modulated data to the IR LED in a transmission sequence.

    Once the data held in uDataArray[] has been fully sent, 'eIDLE' state is resumed and the 'Busy' line is de-asserted (sending data pin D4 high). The device is now ready to receive more button presses marking the end of the transmission sequence.

    Reception of IR button press data

    When data is sent to the InfraRed remote controller via I2C it triggers an interrupt and the receiveEvent() function call is triggered asynchronously.

    Once triggered the received I2C data is written sequentially into the buffer 'uDataArray[]'.

    During data reception, if an end of sequence is signalled by the master (bFreshData!=0x00) the 'bFreshDataFlag' is set, thus signalling the start of the transmission sequence.


    Pictures 2...3 give an example of a typical packet sequence.

    Note : Full source code available here

    Step 3: I2C Packet Content

    The format of the control packet sent to the slave over I2C is given above in picture 1 the meaning of each field is given below

    Meaning of the control packet fields

    byte bEncoding;

    • IR remote control encoding,
      • RC6 (Sky) = 0,
      • SONY = 1,
      • SAMSUNG = 2,
      • NEC = 3,
      • LG = 4

    uint32_t ui32Data;

    • The hex representation of the binary IR data stream 4 Data bytes (unsigned long), LSByte ... MSByte

    byte bNumberOfBitsInTheData;

    • Number of Bits in the data (Max of 32). Range = 1 ... 32

    byte bPulseTrainRepeats;

    • How many repeats of this pulse train. Range = 1 ... 255 . Typically 2...4 repeats. You may want to extend this for On/Off commands as the receiving device sometimes requires a few extra pulse train repeats to receive a turn on signal.

    byte bDelayBetweenPulseTrainRepeats;

    • Delay between repeats of this pulse train. Range = 1 ... 255mS. Typcially 22mS ... 124mS.

    byte bButtonRepeats;

    • Simulates repeated pressing of the same button (but doesn't support the modified code like an Apple remote, it just repeats the button code). Range = 1 ... 256. Default = 1.

    uint16_t ui16DelayBetweenButtonRepeats;

    • Delay between button repeats (unsigned int). 2 bytes in total LSByte ... MSByte. Range = 1 ... 65535mS. Default = 0mS

    byte bFreshData;

    • Fresh data. A non-zero value. Written last, triggers the IR TX sequence. Range 0x00...0xFF
      • More control packets to come = 0
      • This is the final control packet = Non-Zero value 1,2, ... 255


    Note the use of the '__packed__' compiler directive. This is to ensure the data is packet byte for byte in memory irrespective of the target system used (Uno, Due, ESP8266 etc.). This means the union between registerAllocationType and dataArrayType need only sequentially clock out/clock in bytes from a control packet, making the TX/RX software simple.


    Step 4: Acquiring Remote Control Codes (ui32Data)

    There are three ways you can acquire a respective remote control key code;

    1. Via bit counting with an oscilloscope,
    2. Look it up on a website,
    3. Decode it direct from the data stream in software.


    Via bit counting with a scope

    This is not an efficient method as it takes quite some time and potentially requires more than one attempt, however it can be highly accurate. It is also useful in visually validating codes obtained using methods 2 and 3, also in determining any peculiarities of a remote. By way of example when holding down a button on an Apple IR remote. The remote will initially issue a command sequence then follow that with a repeated compressed sequence of 0xF....

    Look it up on a website

    The remote control code database on the Linux Infrared Remote Control web site is a good source.

    The downside however, is you may have to try a few codes until you find one which works for you. You may also have to interpret some of the representations of the codes to convert them into their equivalent hex form.

    Decode it direct from the data stream

    Using the circuit in picture 1 above in conjunction with the IRremote library example 'IRrecvDumpV2.ino' it is possible to decode the data stream direct from the remote. Picture 2 shows a decoded Samsung TV remote for an on/off button press in the Arduino IDE terminal window.


    Combined Receiver/Transmitter

    Pictures 3 and 4 above depict a solution which allows for both reception and transmission of IR command to allow for easy prototyping.

    To decode IR remote control button presses you will need to flash the Arduino with the 'IRrecvDumpV2.ino' example which comes with the IRremote library.

    It also works equally well for transmission if IR commands. A red led is included as a visual indication the device is in action.

    Step 5: How to Test Your I2C Slave Device

    Using the source code here, and the circuit outlined above in picture 1, programme the 'Master' Arduino with 'IR_Remote_Sim_Test.ino' and the 'Slave' Arduino with 'IR_Remote_Sim.ino'.

    Assuming you have a Sony Bravia TV, Sky HD box and a Sony BT SoundBar, press button 1 and your TV will switch to BBC1 (channel 101). Press button 2 and your sound bar will mute. Press again and it will un-mute.

    During execution of the IR transmission sequence LED3 will illuminate indicating the slave is busy and LED1 will flicker inline with the IR transmission process.

    Of course if you don't have the same entertainment system set up as above, you can re-programme the slave with 'IRrecvDumpV2.ino', decode your remote commands of interest, then programme them into the 'IR_Remote_Sim_Test.ino' for your given scenario.

    Picture 2 shows the system level test software overview between Master and Slave.

    Step 6: Shrinking Your Design

    Ok, so assuming you've followed this instructable relying on two Arduinos to control your home devices is not the most efficient use of your Arduino stock. Consequently if you construct the circuit shown in the picture above and follow the instructions here to program the ATMega328P with 'IR_Remote_Sim.ino', you will be able to reduce the whole system to the minimal components. This will allow you to embed your design into some other system.

    Step 7: Conclusion

    The solution is stable and works well, it's been embedded in another system for quite some weeks now without any issues.

    I chose the Arduino Uno R3 as I wanted a device which had sufficient RAM such that I could have a button buffer of reasonable depth. I settled for a buffer size of 20 packets (MAX_SEQUENCES).

    The Hybrid TX/RX shield I made also came in very handy when decoding Sony and Sky remote controls. Though I have to confess using my digital scope from time to time to check the software decoded IR command was the same as that coming from the IR received (TSOP38328).

    The only thing I would have done differently would have been to use the constant current drive circuit for the IR led as shown above in pic 2.

    One further point to note is, not all IR transmitters are modulated with 38KHz, the TSOP38328 is optimised for 38KHz.

    Step 8: References Used