ATtiny13 – IR remote to control LEDs (NEC proto)

This project allows control turn ON/OFF LEDs with IR remote control from your TV. The big part of the code shows how to decode a command and address of NEC protocol from IR signals using a cheap IR receiver. It’s worth to note that the algorithm which is responsible for decoding IR signals is nonblocking and detects repeat-codes. Project has been tested with ATtiny13a @9.6 MHz and VCC=5V. The code is on Github, click here.

Parts Required

Circuit Diagram

011_infrared_receiver_38khz_nec_proto_remote_control

Firmware

This code is written in C and can be compiled using the avr-gcc. More details on how compile this project is here.

/**
 * Copyright (c) 2016, Łukasz Marcin Podkalicki <lpodkalicki@gmail.com>
 * ATtiny13/011
 * Control LEDs with IR remote control. Example of monblocking 
 * IR signal reader (38kHz, TSOPxxx) and NEC protocol decoder.
 * MCU Settings:
 *  FUSE_L=0x7A
 *  FUSE_H=0xFF
 *  F_CPU=9600000
 */

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define    LED1_PIN               PB0
#define    LED2_PIN               PB2
#define    LED3_PIN               PB3
#define    LED4_PIN               PB4

#define    IR_IN_PIN              PB1
#define    IR_IN_PORT             PORTB
#define    IR_OCR0A               (122)

#define    LOW                    (0)
#define    HIGH                   (1)

#define    IR_SUCCESS             (0)
#define    IR_ERROR               (1)

#define    IR_EVENT_IDLE          (0)
#define    IR_EVENT_INIT          (1)
#define    IR_EVENT_FINI          (2)
#define    IR_EVENT_PROC          (3)

#define    IR_PROTO_EVENT_INIT    (0)
#define    IR_PROTO_EVENT_DATA    (1)
#define    IR_PROTO_EVENT_FINI    (2)
#define    IR_PROTO_EVENT_HOOK    (3)

volatile uint16_t IR_timeout = 0;
volatile uint16_t IR_counter = 0;
volatile uint32_t IR_rawdata = 0;

uint8_t IR_event = 0;
uint8_t IR_proto_event = 0;
uint8_t IR_index = 0;
uint32_t IR_data = 0;

static void
IR_init()
{
    DDRB &= ~_BV(IR_IN_PIN); // set IR IN pin as INPUT
    PORTB &= ~_BV(IR_IN_PIN); // set LOW level to IR IN pin
    TCCR0A |= _BV(WGM01); // set timer counter mode to CTC
    TCCR0B |= _BV(CS00); // set prescaler to 1
    TIMSK0 |= _BV(OCIE0A); // enable Timer COMPA interrupt
    OCR0A = IR_OCR0A; // set OCR0n to get ~38.222kHz timer frequency
    GIMSK |= _BV(INT0); // enable INT0 interrupt handler
    MCUCR &= ~_BV(ISC01); // trigger INTO interrupt on raising
    MCUCR |= _BV(ISC00); // and falling edge
    sei(); // enable global interrupts
}

static int8_t
IR_NEC_process(uint16_t counter, uint8_t value)
{
    int8_t retval = IR_ERROR;

    switch(IR_proto_event) {
    case IR_PROTO_EVENT_INIT:
        /* expecting a space */
        if (value == HIGH) {
            if (counter > 330 && counter < 360) {
                /* a 4.5ms space for regular transmition of NEC Code; 
                 counter => 0.0045/(1.0/38222.0) * 2 = 344 (+/- 15) */
                IR_proto_event = IR_PROTO_EVENT_DATA;
                IR_data = IR_index = 0;
                retval = IR_SUCCESS;
            } else if (counter > 155 && counter < 185) {
                /* a 2.25ms space for NEC Code repeat;
                 counter => 0.00225/(1.0/38222.0) * 2 = 172 (+/- 15) */
                IR_proto_event = IR_PROTO_EVENT_FINI;
                retval = IR_SUCCESS;
            }
        }
        break;
    case IR_PROTO_EVENT_DATA:
        /* Reading 4 octets (32bits) of data:
         1) the 8-bit address for the receiving device
         2) the 8-bit logical inverse of the address
         3) the 8-bit command
         4) the 8-bit logical inverse of the command
        Logical '0' – a 562.5µs pulse burst followed by a 562.5µs 
         (<90 IR counter cycles) space, with a total transmit time of 1.125ms
        Logical '1' – a 562.5µs pulse burst followed by a 1.6875ms
         (>=90 IR counter cycles) space, with a total transmit time of 2.25ms */
        if (IR_index < 32) {
            if (value == HIGH) {
                IR_data |= ((uint32_t)((counter < 90) ? 0 : 1) << IR_index++);
                        if (IR_index == 32) {
                    IR_proto_event = IR_PROTO_EVENT_HOOK;
                }
            }
            retval = IR_SUCCESS;
        }
        break;
    case IR_PROTO_EVENT_HOOK:
        /* expecting a final 562.5µs pulse burst to
         signify the end of message transmission */
        if (value == LOW) {
            IR_proto_event = IR_PROTO_EVENT_FINI;
            retval = IR_SUCCESS;
        }
        break;
    case IR_PROTO_EVENT_FINI:
        /* copying data to volatile variable; raw data is ready */
        IR_rawdata = IR_data;
        break;
    default:
        break;
    }

    return retval;
}

static void
IR_process()
{
    uint8_t value;
    uint16_t counter;

    /* load IR counter value to local variable, then reset counter */
    counter = IR_counter;
    IR_counter = 0;

    /* read IR_IN_PIN digital value 
     (NOTE: logical inverse value = value ^ 1 due to sensor used) */
    value = (PINB & (1 << IR_IN_PIN)) > 0 ? LOW : HIGH;

    switch(IR_event) {
    case IR_EVENT_IDLE:
        /* awaiting for an initial signal */
        if (value == HIGH) {
            IR_event = IR_EVENT_INIT;
        }
        break;
    case IR_EVENT_INIT:
        /* consume leading pulse burst */
        if (value == LOW) {
            if (counter > 655 && counter < 815) {
                /* a 9ms leading pulse burst, NEC Infrared Transmission Protocol detected,
                counter = 0.009/(1.0/38222.) * 2 = 343.998 * 2 = 686 (+/- 30) */
                IR_event = IR_EVENT_PROC;
                IR_proto_event = IR_PROTO_EVENT_INIT;
                IR_timeout = 7400;
            } else {
                IR_event = IR_EVENT_FINI;
            }
        } else {
            IR_event = IR_EVENT_FINI;
        }
        break;
    case IR_EVENT_PROC:
        /* read and decode NEC Protocol data */
        if (IR_NEC_process(counter, value))
            IR_event = IR_EVENT_FINI;
        break;
    case IR_EVENT_FINI:
        /* clear timeout and set idle mode */
        IR_event = IR_EVENT_IDLE;
        IR_timeout = 0;
        break;
    default:
        break;
    }

}

static int8_t
IR_read(uint8_t *address, uint8_t *command)
{
    if (!IR_rawdata)
        return IR_ERROR;

    *address = IR_rawdata;
    *command = IR_rawdata >> 16;
    IR_rawdata = 0;

    return IR_SUCCESS;
}

ISR(INT0_vect)
{

    IR_process();
}

ISR(TIM0_COMPA_vect)
{
    /* When transmitting or receiving remote control codes using
     the NEC IR transmission protocol, the communications performs
     optimally when the carrier frequency (used for modulation/demodulation)
     is set to 38.222kHz. */
    if (IR_counter++ > 10000)
        IR_event = IR_EVENT_IDLE;
    if (IR_timeout && --IR_timeout == 0)
        IR_event = IR_EVENT_IDLE;
}

int
main(void)
{
    uint8_t addr, cmd;

    /* setup */
    DDRB |= _BV(LED1_PIN)|_BV(LED2_PIN)|_BV(LED3_PIN)|_BV(LED4_PIN);
    IR_init();

    /* loop */
    while (1) {
        if (IR_read(&addr, &cmd) == IR_SUCCESS) {
            if (addr != 0x01)
                continue;
            switch(cmd) {
            case 0x01:
                /* turn all LEDs off */
                PORTB &= ~(_BV(LED1_PIN)|_BV(LED2_PIN)|_BV(LED3_PIN)|_BV(LED4_PIN)); 
                break;
            case 0x00:
                PORTB ^= _BV(LED1_PIN); // toggle LED1
                break;
            case 0x07:
                PORTB ^= _BV(LED2_PIN); // toggle LED2
                break;
            case 0x06:
                PORTB ^= _BV(LED3_PIN); // toggle LED3
                break;
            case 0x04:
                PORTB ^= _BV(LED4_PIN); // toggle LED4
                break;
            default:
                break;
            };
        }
    }
}

22 thoughts on “ATtiny13 – IR remote to control LEDs (NEC proto)

  1. Дружище а можно поправить код , нужно чтоб кнопки пульта работали без фиксации при этом светодиоды должны гореть постоянно соответственно после отпускания кнопок светодиоды должны выключиться

  2. Hello bro, many thanks for that tuto, but I have a question:
    After compiling the code with your makefile I found the .hex file is 2.2Kb and Attiny13 only has 1K flash, so, how did you burn the code onto it !!?

  3. Alguien puediera ayudar con esto, estoy usando WinAVR para compilar el código; si alguien supiera de cual está mal me avise por favor soy nuevo en esto mi correo es alfredoperez8709@gmail.com
    gracias de antemano a cualquier opinión es bienvenida.

    [main] sh 12012 sync_with_child: child 6476(0x1E4) died before initialization with status code 0xC0000142
    10793 [main] sh 12012 sync_with_child: *** child state waiting for longjmp
    /usr/bin/sh: fork: Resource temporarily unavailable
    0 [main] sh 8740 sync_with_child: child 1452(0x1E4) died before initialization with status code 0xC0000142
    31178 [main] sh 8740 sync_with_child: *** child state waiting for longjmp
    /usr/bin/sh: fork: Resource temporarily unavailable

    ——– begin ——–
    avr-gcc (WinAVR 20100110) 4.3.3
    Copyright (C) 2008 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    POSIBLE ERROR ESTA EN ESTA PARTE
    Compiling C: main.c
    avr-gcc -c -mmcu=attiny45 -I. -gdwarf-2 -DF_CPU=1000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./main.lst -std=gnu99 -MMD -MP -MF .dep/main.o.d main.c -o main.o
    main.c:51: warning: function declaration isn’t a prototype
    main.c:123: warning: function declaration isn’t a prototype
    main.c:238: fatal error: opening dependency file .dep/main.o.d: No such file or directory
    compilation terminated.
    make.exe: *** [main.o] Error 1

  4. I do upload.. but i dont have that nec remote. S0 it turns nothing… .. i hope there couldbe a code you could actualy select the key ir code… so dumb of me.. please help..

  5. Sorry ı m new on attiny. which lines defined certain buttons nec code of ır remote controller . ı want to change for my ır device. and ı didnt find but, is there any project to change led bright using same way.

  6. ayuda alguien que tenga conocimiento acerca de este error en WinAVR , trato de compilar el codigo fuente que esta publicado en este blog, pero me sales lo siguiente mensaje

    ompiling C: main.c
    avr-gcc -c -mmcu=attiny45 -I. -gdwarf-2 -DF_CPU=9600000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./main.lst -std=gnu99 -MMD -MP -MF .dep/main.o.d main.c -o main.o
    main.c:51: warning: function declaration isn’t a prototype
    main.c: In function ‘IR_init’:
    main.c:56: error: ‘TIMSK0’ undeclared (first use in this function)
    main.c:56: error: (Each undeclared identifier is reported only once
    main.c:56: error: for each function it appears in.)
    main.c: At top level:
    main.c:123: warning: function declaration isn’t a prototype
    main.c:238: fatal error: opening dependency file .dep/main.o.d: No such file or directory
    compilation terminated.
    make.exe: *** [main.o] Error 1

  7. Thank you for a wonderful series of articles.

    In my case, IR Recivier works only when sending a replay after the main message
    I think, if we set event = IR_PROTO_EVENT_HOOK and leave the IR_NEC_process, we will no longer enter the IR_NEC_process and will not reach IR_PROTO_EVENT_FINI, since the impulse (interrupt) is no longer expected

    If set IR_proto_event to IR_PROTO_EVENT_FINI while end processing IR_PROTO_EVENT_DATA, all works as expected

    • Alexander, thank you for sharing the improvement! I marked this example as “require review” in future and will try to reproduce that case.
      /L

      • Thankyou very match!..

        #Łukasz Podkalicki# Can i know how to program Attiny13a ic with ir receiver using ARDUINO?

        • Hi, you can try make an experiment w/ libraries that are available for Arduino. However, all my work regarding Attiny13 is to make the firmware small size what can be hard to achieve using Arduino code.

  8. Hi friends, I want to try at home, whether the remote control used may be different? is there any way of setting it if using different remote control? thank you

  9. hi ,
    i am prashant from india.i have deployed NEC protocol on ATTINY13A controller using above steps and code, but i am facing range issue, when i am trying to operate receiver from 4-5 feet its not responding.how to tackel this issue

    • Hi, due to ATtiny13 is using internal oscillator it needs sometimes a little callibration (incr/decr the value of IR_OCR0A) to work with valid frequency.

  10. Can I use an Universal controller? If that’s the case which brand should i use, or what control are you using?

Leave a Comment