Programmers

All on one SLOW page
Serial Port
Parallel Port
Smart Programmers
Serial Loaders

Projects

ATmega8 Serial LCD
ATtiny2313 Serial LCD
ATtiny4313 Serial LCD
ATmega328 SIRC
ATtiny2313 SIRC
40-pin Dev Board
28-pin Dev Board
AVR PS/2 Keyboard
AVR MAX232 RTS/CTS
AVR Dual RS232 Ports

Minimal Circuits

ATmega16
ATmega32
ATmega644
ATmega1284
ATmega8515
ATmega8535
ATmega8
ATmega48
ATmega88
ATmega168
ATmega328
ATmega162
ATmega128
ATtiny13
ATtiny2313
ATtiny4313
ATtiny24
ATtiny84
ATtiny25
ATtiny45
ATtiny85

Other Stuff

ATtiny13 vs ATtiny85
ATmega8 vs ATmega88
ATmega16 vs ATmega164
ISP and SPI
MAX232 Arduino
A small FAQ
Hardware Info

SIRC using an ATtiny2313

Including a few interruptions, it took around 45 minutes to build (mechanically) this project. It uses a single LED to prove it's point, but you could put up to 16 individual controlled device functions on the ATtiny2313. The ATtiny24 could be used if fewer I/O lines are needed. The SIRC routines and the main() loop take 592 bytes. The SIRC routines are all interrupt driven, so all you need to do is check a flag to see if there is a command available or not. If a command is available, process it and then set the flag to false. The Sony remote repeats the code three times. The flag will only be set if the same command is received in three consecutive packets. It has been 100% reliable in tests.

Complete c source code

Button "1" turns the LED on and button "2" turns it off. The numbers are zero normalized from the remote (button 1 = 0, 2 = 1, ..., 0 = 9). The ATtiny2313 has two external interrupts. We're using INT1. The ATtiny2313 also has two timers - one 8-bit and one 16-bit. We're using the 8-bit timer0 with a prescaler set to 256. Be sure to remove the system clock divide by 8 (bit 7 in the low fuse byte).

Download the complete ATtiny2313 SIRC project here.


//-------------------------------------------------------------------------------------------------

// sirctiny.c

// An SIRC process for the ATtiny24/44/84 MCU's

// 488 bytes of Flash + 16 bytes of RAM.

//-------------------------------------------------------------------------------------------------

#include <avr/interrupt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sirctiny.h"

#define RISE 1
#define FALL 0

#ifndef F_CPU
#error no F_CPU defined
#endif

#ifndef __AVR_ATtiny2313__
#ifndef __AVR_ATtiny84__
#error no MCU defined
#endif
#endif

unsigned char _bit_time;
unsigned char _bit_count;
unsigned char _last_command;
unsigned char _first_command;
unsigned char _bit_value;
unsigned char _inBit;
unsigned char _edge;
unsigned char deviceAddress;
volatile unsigned char address;
volatile unsigned char command;
volatile unsigned char extra;
volatile unsigned char commandReady;

// These ATtiny2313 constants have to change for different MCUs.

#define TIM0_VECT TIMER0_OVF_vect
#define INT_VECT INT1_vect
#define MSK0 TIMSK
#define INT_DR PORTD
#define INT_DDR DDRD
#define INT_PIN 8

//-------------------------------------------------------------------------------------------------

// Timer0 interrupt handler

// Timer0 is used to time the bit high and low times. If it ever expires, the frame is finished.
// That is when this piece of code runs.

//-------------------------------------------------------------------------------------------------

ISR( TIM0_VECT )
{
	MSK0 &= ~(1<<TOIE0);		// Disable the timer interrupt.

	command >>= 1;			// Right justify the 7-bit command.
	address >>= 3;			// Right justify the 5-bit address
	if ( _bit_count == 15 )
	{
		address &= extra;	// Only if there is extra data, like 20-bits.
	}

	if ( !commandReady )
	{
		// Verify the command is for this address (This Sony is address 1)
		if ( address == deviceAddress )
		{
			// We have to have three consecutive identical values for it to be considered
			// a valid press.
			if (( _first_command ^ _last_command ^ command ) == command )
			{
				commandReady = 1;
			}
			_last_command = _first_command;
			_first_command = command;
		}
	}
}

//-------------------------------------------------------------------------------------------------

// rising_edge_routine()

// The INT0 rising edge interrupt handler. Fires on the rising edge (end of bit). All the bit
// processing is done here.

// 8.000 MHz clock / 256 = 31250 ( 0.000032 S. )
// 0.6 mS = 18.75
// 1.2 mS = 37.5
// 2.4 mS = 75.0
//-------------------------------------------------------------------------------------------------

void rising_edge_routine( )
{
	_bit_time = TCNT0;				// Save the bit duration.
	_inBit = 0;					// No longer timnig a bit.
	_bit_value = 0;					// Default to a "zero" bit.

	if (( _bit_time > 30 ) && ( _bit_time < 50 )) {
		_bit_value = 128;			// It is a "one" bit.
	}
	else if (( _bit_time > 60 ) && ( _bit_time < 100 )) {
		_bit_count = 0;				// It is the "start" bit.
		address = 0;				// Reset everything.
		extra = 0;
		return;
	}
	if ( _bit_count < 7 )				// Still in command?
		command = (command >> 1) + _bit_value;
	else if ( _bit_count < 12 )			// Still in address?
		address = (address >> 1) + _bit_value;
	else if ( _bit_count < 20 )			// Still in extra data?
		extra = (extra >> 1) + _bit_value;
	++_bit_count;
}

//-------------------------------------------------------------------------------------------------

// falling_edge_routine( )

// The INT0 falling edge interrupt handler. The timer is reset and we start the bit.

//-------------------------------------------------------------------------------------------------

void falling_edge_routine( )
{
	MSK0 |= (1<<TOIE0);			// Start the timer.
	_inBit = 1;				// We're timing a bit now.
	TCNT0 = 0;				// Start counting at zero.
}

//-------------------------------------------------------------------------------------------------

// INT0 interrupt handler.

// Calls the appropriate edge routine, based on where in a bit we are.

//-------------------------------------------------------------------------------------------------

ISR( INT_VECT )
{

	if ( _edge == FALL )
	{
		falling_edge_routine( );	// Start up the bit timer.
		_edge = RISE;			// Ready for falling edge.
		MCUCR |= 0x0c;			// Setup INT1 for falling edge.

	}
	else
	{
		rising_edge_routine( );		// Process the bit.
		_edge = FALL;			// Setup for the next falling edge.
		MCUCR &= ~0x0c;			// Setup INT1 for the falling edge.
		MCUCR |= 0x08;
	}
}

//-------------------------------------------------------------------------------------------------

// sircInit( )

// Initialize the variables used in the routines.
// Set up the port.
// Turn on the interrupts.

//-------------------------------------------------------------------------------------------------

void sircInit( void ) 
{

	// Initialize the variables.
	deviceAddress = 1;		// Our Sony TV remote code uses address 1.
	command = 0;			// No command yet.
	address = 0;			// No address yet.
	extra = 0;			// No extra data.
	_bit_count = 0;			// No bits received.
	_inBit = 0;			// Not measuring a bit yet.
	commandReady = 0;		// No command is available to process.

	// Initialize the timer and divide by 256 prescaler (with 8 MHz clock, set by lfuse).
	TCCR0A = 0;			// Set the prescaler.
	TCCR0B |= 0x04;

	// Set up the port.
	INT_DDR &= ~INT_PIN;		// INT1 is input.
	INT_DR |= INT_PIN;		// Set the bit high to enable pullup on INT1 input.

	MCUCR &= ~0x0c;			// Set up for falling edge interrupt on INT1.
	MCUCR |= 0x08;
	GIMSK |= 0x80;			// Enable INT1.
	
	// Enable interrupts.
	sei( );
}

//-------------------------------------------------------------------------------------------------

// main()

// Blink the active low LED during initialization.
// Initialize the SIRC routines.
// Loop waiting for a command to become ready to process.
// Turn LED on when the "1" button is pressed, off when the "2" button is pressed.
//-------------------------------------------------------------------------------------------------

int main( void )
{
	DDRB |= 1;
	PORTB &= ~1;	// Debug LED ON
	
	// Init the SIRC code.
	sircInit( );

	PORTB |= 1;	// Debug LED OFF

	// Main loop.
	while( 1 )
	{
		if ( commandReady == 1 )
		{
			switch ( command )
			{
				case 0:
					PORTB &= ~1;	// Debug LED ON
					break;
				case 1:
					PORTB |= 1;	// Debug LED OFF
					break;
				default:
					break;
			}
			command = 0;
			commandReady = 0;
		}
	}
	return 1;
}