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;
            };
        }
    }
}

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

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

  2. 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.

  3. 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

      • 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.

  4. 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

Leave a Comment