Arduino IR transmitter

A while back, I bought a universal remote for the entertainment system. I chose one fancy enough to have learning capability, but not fancy enough to be programmed with a computer.

Annoyingly, the factory and universal remote only had a button to toggle through the various inputs. This gets tedious quickly when you have a few video game consoles. I knew that the TV had discrete codes, which would take you directly to a selected input. But I didn’t have a way to program the remote with these codes.

So I wrote an IR transmitter for Arduino. Then I could use the Arduino to program the remote, and have nice discrete component access straight from the remote that I’m already using. My TV is a Samsung, which uses the . It’s a pretty simple protocol consisting of two address bytes and two command bytes, with a 9 ms burst ahead of it. Ones and zeros are encoded as different pauses after a short burst at 38 kHz. My previous IR project was very helpful for debugging timing issues.

Download the code. The only hardware required is an IR LED attached to pin 13.

2 thoughts on “Arduino IR transmitter”

  1. A few comments that might help others who look at this…

    This program needs you to key in the OBC codes (in the serial monitor). At first I was typing in the EFC codes that OFA remotes use. They won’t work here.

    Here are the OBC codes for the more useful functions that don’t appear on any keys on your original Samsung TV remote:

    Discrete ON 153
    Discrete OFF 152
    Component 1 134
    Component 2 136
    HDMI 1 233
    HDMI 2 190
    HDMI 3 194
    PC 105
    AV 1 133
    AV 2 237

    These worked on my model LN-Txx61F Samsung TV. Its device code was also 7 like the program has it, but yours might be different. You might run Lucas’ IRMON sketch to find out what it is.

    I had luck programming my learning remote by transmitting the code just once (leaving CONTINUOUS undefined). YMMV. I’m not sure if Samsung complies with the special repeat code in the NEC protocol for when you hold down a key, but this program doesn’t do repeats the NEC way.

    I also moved the IR LED from pin 13 to 2 so I could use a smaller resistor and get a more powerful signal. In the modulate() function, I used PORTD and a mask of 0x04 to control pin 2.

    Note that the NEC protocol specifies that ON_START_TIME is 9000, but Samsung deviates from this and uses 4500.

    Many thanks, Lucas.

  2. I used your code and made a few changes. I need to control two DCT700 tuners with Mythtv. I’m not a code writer, so I used your existing remote variable to select which output pin is used. I also noticed that the modulate portion was overly complex, and I had a oscope to measure the frequency and removed all the PORTB stuff. Thanks for putting this up, I was tried of using LIRC and this is much better for transmitting to both TV receivers.

    /*
     * DCT700 dual /Arduino IR blaster.
     * 
     * Copyright (c) 2009 Alex Williamson
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of version 2 of the GNU General Public License as
     * published by the Free Software Foundation, 59 Temple Place, Suite 330,
     * Boston, MA 02111-1307 USA
     * 
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     * 
     * For the complete license, please see http://www.fsf.org/licenses/gpl.txt
     * or request a copy from the author of this program.
     */
     
     /* I modified this code to modulate at exactly 38khz and use two output pins to control two DCT700’s */

    #include <util/delay.h>

    /* From DCT700 remote lirc files, times in us */
    #define PHEAD 9000
    #define SHEAD 4400
    #define PONE 550
    #define SONE 4400
    #define PZERO 550
    #define SZERO 2150
    #define PTRAIL 550
    /* for some reason the DCT700 doesnt register a zero unless you send a repeat pulse after a delay ETRAIL is the delay */
    #define ETRAIL 42050
    #define GAP 11000

    #define DATA_BITS 16
    #define POST_BITS 0

    #define PERIOD_US 27  // ~= (1/56kHz) * 1000000

    #define DEFAULT_REMOTE 0x000
    #define DEFAULT_REPEAT 0
    #define DEFAULT_DELAY 250

    int IR_PIN;
    /*
     Changed this to digitalWrite and adjusted PERIOD_US by measuring frequency on OSCOPE
     */
     
    static void modulate(int time)
    {
    int count = time / PERIOD_US;
            for(int i = 0; i <= count; i++) {
            digitalWrite(IR_PIN, HIGH);
            delayMicroseconds(10);
            digitalWrite(IR_PIN, LOW);
            delayMicroseconds(10);
            }

    }

    static void inline send_bit(int bit)
    {
    if (bit) {
                   /* Serial.print(“1”);*/
    modulate(PONE);
    _delay_loop_2((SONE * 4) – 440);
    } else {
                  /* Serial.print(“0”); */
    modulate(PZERO);
    _delay_loop_2((SZERO * 4) – 250);
    }
    }

    static byte inline wait_for_byte()
    {
    while (!Serial.available());
    return Serial.read();
    }

    static void usage(void)
    {
    Serial.print(“Arduino dish IR blaster\n”);
    Serial.print(“Default remote code: “);
    Serial.print(DEFAULT_REMOTE, DEC);
    Serial.print(“\nDefault repeat count: “);
    Serial.print(DEFAULT_REPEAT, DEC);
    Serial.print(“\nDefault post command delay: “);
    Serial.print(DEFAULT_DELAY, DEC);
    Serial.print(“(ms)\nCommand format: ”
         “[#][@][d].\n");
    Serial.print("Values in decimal\n");
    }

    static int get_command(int *code, int *repeat, int *remote, int *delay_ms)
    {
    byte data;
    int *cur;

    *code = 0;
    *repeat = DEFAULT_REPEAT;
    *remote = DEFAULT_REMOTE;
    *delay_ms = DEFAULT_DELAY;

    cur = code;
    while (data = wait_for_byte()) {
    switch (data) {
    case '#':
    cur = repeat;
    *cur = 0;
    break;
    case '@':
    cur = remote;
    *cur = 0;
    break;
    case 'd':
    cur = delay_ms;
    *cur = 0;
    break;
    case '.':
    return 0;
    case '0' ... '9':
    *cur *= 10;
    *cur += (data - '0');
    break;
    default:
    usage();
    return -1;
    }
    }  
    }

    void setup()
    {
            IR_PIN = 13;
    pinMode(IR_PIN, OUTPUT);
    digitalWrite(IR_PIN, LOW);
            IR_PIN = 12;
    pinMode(IR_PIN, OUTPUT);
    digitalWrite(IR_PIN, LOW);

    Serial.begin(115200);
    delay(500);
    }

    void loop()
    {
    int i, code, repeat, remote, delay_ms;
    unsigned int mask;

    while (get_command(&code, &repeat, &remote, &delay_ms) != 0);

    /*
     * Wait for serial to settle.  If we get a long stream of
     * commands, we may start executing while bytes are still
     * coming in, this can affect the timing.
     */
    do {
    i = Serial.available();
    delay(1);
    } while (i != Serial.available());
             
            /* used the "remote" potion of the code to switch between the two transmitters */
            switch (remote) {
               case 64:
                IR_PIN = 12;
            break;
               default:
              IR_PIN = 13;
            }
                
            modulate(PHEAD);
    _delay_loop_2(SHEAD * 4);

    do {
    for (mask = 0x1 <>= 1)
    send_bit(code & mask);

    for (mask = 0x1 <>= 1)
    send_bit(remote & mask);

    modulate(PTRAIL);
    /*_delay_loop_2((GAP * 4) - 1100);
                    *
                    * I found that for the DCT700, I could replace the GAP with the ETRAIL,
                    * this is the only way I could get 0 to register. 
                    *
                    */
                    _delay_loop_2((ETRAIL * 4) - 1000);
                    modulate(PHEAD);
            _delay_loop_2(SZERO * 4);
                    modulate(PONE);

    } while (--repeat > 0);
    Serial.print("\n");
    delay(delay_ms);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *