Sony SIRC infrared protocol decoder for ATTiny85

Recently, i had to decode sony SIRC encoded IR signals with an ATTiny85 microcontroller.

First i tried to use the IRRemote library, it was i real pain because it's not designed with ATTiny85 in mind (Master version 2.0.1 seems to work with attiny85).

Some modified versions or other libraries exists but then it takes up to 99% of the program space, needless to say it's unusable.

So i decided to take a look at how the IR signal is encoded and to write my own decoder.

Versions

There are three versions of the protocol (12, 15 and 20 bits).

Each version are composed of a Command and an Address word.
The 20 bits version also have an Extended word.

SIRC Frame versions

Frame Timing

Each frame is composed of 600┬Ás width T pulses.

The frame start with a 4T mark pulse.
Each bit in the frame is encoded by a 1T space followed by a 1T mark (if the bit is 0) or a 2T mark (if the bit is 1).

SIRC Frame pulses

Source Code

This is the decoder source code for the 12 bits version, it can be easily modified to decode other versions.

This code once compiled only uses 1690 bytes of the program space and 28 bytes of dynamic memory.

// IR Sensor input pin
const unsigned char IR_SENSOR_PIN = PB3;

// IR decoder state machine
const unsigned short IR_T = 600;  
const unsigned char IR_START_T = 4;  
const unsigned char IR_BIT_0_T = 1;  
const unsigned char IR_BIT_1_T = 2;  
const unsigned char IR_N_BITS = 12;  
const unsigned char IR_DECODER_STATE_WAITING = 0;  
const unsigned char IR_DECODER_STATE_DECODING = 1;

volatile unsigned char irMessageReady = 0;  
volatile unsigned short irMessage = 0;

void setup() {  
  pinMode(IR_SENSOR_PIN, INPUT);

  PCMSK |= _BV(PCINT3); // Set PB3 pin change interrupt bit
  GIMSK |= _BV(PCIE);
  MCUCR = (0 << ISC01) | (1 << ISC00); // Trigger on any edge
}

void loop() {  
  if (irMessageReady == 1) {
    // Do something with the message
    unsigned short command = (irMessage >> 5) & 0x7F;
    unsigned short address = irMessage & 0x1F;
    irMessageReady = 0;
  }
}

ISR(PCINT0_vect) {  
  static unsigned char irDecoderState = IR_DECODER_STATE_WAITING;
  static unsigned long lastInputStateChange = micros();
  static unsigned char bitCounter = 0;
  static unsigned short message = 0;

  unsigned long now = micros();
  unsigned char inputState = digitalRead(IR_SENSOR_PIN);
  unsigned char T = ((now - lastInputStateChange) / IR_T) + 0.5f;

  // My IR sensor uses inverted logic
  // Based on my drawing this is the falling edge of the signal
  if (inputState == HIGH) {
    switch (irDecoderState) {
      // Are we waiting for a start pulse ?
      case IR_DECODER_STATE_WAITING:
        {
          // Did we found a start pulse ?
          if (T == IR_START_T) {
            // Reset the bit counter & the message buffer
            bitCounter = 0;
            message = 0;
            // Change the state of the machine to decoding
            irDecoderState = IR_DECODER_STATE_DECODING;
          }
        }
        break;
      case IR_DECODER_STATE_DECODING:
        {
          if (T == IR_BIT_0_T) { // Is it a bit 0 ?
            // Shift left the buffer of one bit
            message <<= 1;
          } else if (T == IR_BIT_1_T) { // Is it a bit 1 ?
            // Shift left the buffer of one bit
            message <<= 1;
            // and set the least significant bit to 1
            message |= 1;
          }

          // Increment the bit counter and check if we have decoded enough bits
          if (++bitCounter == IR_N_BITS) {
            irMessageReady = 1;
            irMessage = message;
            // Move back the state of the machine to waiting
            irDecoderState = IR_DECODER_STATE_WAITING;
          }
        }
        break;
    }
  }

  lastInputStateChange = now;
}

References

http://www.sbprojects.com/knowledge/ir/sirc.php
http://www.cypress.com/file/58421/download