Infrared Digital Communication

Created by Jonathan Richardson, last modified on Aug 07, 2013

Protocol

Infrared (IR) light is everywhere in the world and just about everything emits some type of infrared light. This creates a noisy environment in the infrared spectrum and it deems a common question: How can infrared noise be omitted? The basic answer has two parts but requires some knowledge of where infrared comes from.

Infrared is mostly non-visible to humans because the wavelength is larger than what our eyes are capable of seeing. Visible light spans from around 400 nanometers to 750 nanometers. Infrared is the next band up, spanning from 750 nanometers to 1 millimeter. The first step of filtering the noise out of an infrared signal is to squint our ‘eyes’ and only look at a narrow band of the infrared frequencies. This is done by picking an infrared transmitter and receiver that operate in a limited frequency band. This step alone removes a large portion of noise.

The second step in noise reduction is done with modulation. Modulation involves toggling the signal on and off rapidly to send a pulse instead of bringing the signal high the entire time. Figure 1 shows how a pulse would look when modulated at 38 kHz.

Figure 1. Closeup of Modulated Signal

The above signal would appear to be a ‘one’ on the receiving end after using a band-pass filter centered at 38 kHz. By using the filter, anything not modulated is excluded. Figure 2 shows a modulation signal being sent and Figure 3 shows how the inverted signal is seen on the receiving side (highlighted). The signal was transmitted over ten feet away and we were still able to obtain a very clean signal using this method. Most television remote controllers use this same exact method of transmitting the signal.

Figure 2. Modulated Transmitted Signal

Figure 3. Inverted Demodulated Received Signal after Comparator

Transmitting Example

Transmitting a digital signal requires modulation to reduce outside noise in the system. We settled on using a microcontroller to modulate and create the signal due to the ease of modification during testing. Following is our transmitter circuit along with the code we used to send a 4-bit BCD code. The infrared LED is toggled at twice the target modulation frequency.

Circuit

BOM

Name Value Usage Digi-Key Part Number
C1, C2 22 pF Loading capacitors for crystal oscillator BC1005CT-ND
C3 100 µF Voltage source smoothing capacitor P15783CT-ND
R1 (5 V) 33 Ω Current limiting resistor for LED for 5V source PPC330BCT-ND
R1 (3.3 V) 6.8 Ω Current limiting resistor for LED for 3.3V source PPC6.8BCT-ND
L1 - Infrared LED 751-1203-ND
Q1 - NPN transistor switch for infrared LED P2N2222AGOS-ND
U1 ATTiny85 Microcontroller Generate modulated signal ATTINY85-20PU-ND
X1 8 MHz Crystal oscillator CTX900-ND

Code

//ATTiny85 Infrared BCD Transmitter
//Designed to run at 8 MHz
//Jonathan Richardson

///----------------CONFIG----------------///
#define TOWER_CODE 2	//Tower 1-4
#define PORT DDB2
///-------------END CONFIG--------------///


///-------------DO NOT EDIT-------------///
#define F_CPU 8e6
#define AFREQ 38e3
#define REG round(F_CPU/2.0/AFREQ)-1
#define TOWER TOWER_CODE-1

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

volatile int counters = 0;
volatile int statusflag = 0;
volatile int highdelay = 0;

#define startbit	114	//1.5ms
#define spacingbit	38	//0.5ms
#define zerobit		38	//0.5ms
#define onebit		76	//1ms
#define codebit		38	//0.5ms

ISR(TIMER0_COMPA_vect)
{
	counters++;

	//check if start bit needs to be sent
	if(statusflag == 0)
	{
		if(counters <= startbit) //start bit
		{
			//toggle the port causing modulation
			PORTB ^= 1<<PORT;
		}
		else
		{
			if(counters <= startbit + spacingbit ) //startbit + spacing
			{
				//turn led off
				PORTB = 0<<PORT;
			}
			else
			{
				counters = 0;
				statusflag = 1;
			}
		}
	}
	//check if full code has been sent
	else if(statusflag > 4)
	{
		if(counters <= codebit-spacingbit)
		{
			//turn led off
			PORTB = 0<<PORT;
		}
		else
		{
			counters = 0;
			statusflag = 0;
		}
	}
	//send the one's and zero's
	else
	{
		highdelay = statusflag-1 == TOWER ? onebit : zerobit ;
		if(counters <= highdelay)
		{
			PORTB ^= 1<<PORT;
		}
		else
		{
			if(counters <= spacingbit+highdelay) //delay above + 600us for time break
			{
				PORTB = 0<<PORT;
			}
			else
			{
				counters = 0;
				statusflag++;
			}
		}
	}

}

int main()
{
	int REGLOAD;

	//Set PB3 to output
	DDRB = (1<<PORT);

	//Interrupt Setup
	TCCR0B |= (1<<CS00);
	TCCR0A |= (1<<WGM01);
	TCNT0 = (0x00);
	TIMSK |= (1<<OCIE0A);
	OCR0A = REG;

	//Enable Interrupts
	sei();

	//interrupts handle everything on
	while(1);

	return 0;
}

Receiving Example

The receiving circuit is made up of an infrared receiver and a comparator. The receiver chosen contains a band pass filter as well as a comparator all in the same package. This makes the signal processing very simple. A second comparator was added after the sensor to eliminate any extra noise in the circuit, this component can be omitted in many applications when using a high-quality infrared receiver. The signal outputted from the comparator is a stable digital signal shown in Figure 3 above. This signal is then processed by a microcontroller using the finite state machine outlined below.

Circuit

BOM

Name Value Usage Digi-Key Part Number
D1 - Infrared Receiver 425-2528-ND
R1 18k Voltage Divider Resistor P18KBBCT-ND
R2 10k Voltage Divider Resistor CF14JT10K0CT-ND
U1 - Comparator to smooth out any noise in signal MAX908CPD±ND

Finite State Machine

Code

#define STATE_WAIT_FOR_START    0
#define STATE_SETTLE_ON_LOW     5
#define STATE_COUNT_BITS        6
#define STATE_WAIT_FOR_HIGH     7
#define STATE_SETTLE_ON_HIGH    8
#define STATE_WAIT_FOR_LOW      9
#define STATE_CHECK_BITSET      10

typedef struct {
	volatile int lowcount;
	volatile int waitcount;
	volatile int currentstatus;
	volatile int state;
	volatile int position;
	volatile int bitset[4];
} IRSensor;

void initIRSensor (IRSensor *Sensor)
{
	int i = 0;
	Sensor->lowcount = 0;
	Sensor->waitcount = 0;
	Sensor->currentstatus = 1;
	Sensor->state = STATE_WAIT_FOR_START;
	Sensor->position = 0;
	for(i = 0; i < 4; i++)
	Sensor->bitset[i] = 0;
}

//Allocate memory to IR Sensor
IRSensor Sensor1;
IRSensor Sensor2;


void readIRSensor (IRSensor *Sensor)
{
	switch(Sensor->currentstatus)
	{
		default: //STATE_WAIT_FOR_START
		if(_RD3 == 0 )
		{
			int i = 0;
			Sensor->position = 0;
			Sensor->currentstatus = STATE_SETTLE_ON_LOW;
			Sensor->waitcount = 0;
			for(i = 0; i < 4; i++)
			Sensor->bitset[i] = 0;
		}
		break;
		case STATE_SETTLE_ON_LOW:
		Sensor->waitcount++;
		if(Sensor->waitcount > 2)
		{
			if(_RD3 == 0)
			{
				Sensor->currentstatus = STATE_COUNT_BITS;
				Sensor->waitcount = 0;
			}
			else
			Sensor->currentstatus = STATE_WAIT_FOR_START;
		}

		break;
		case STATE_COUNT_BITS:
		if(_RD3 == 0)
		Sensor->waitcount++;
		else
		{
			//if a zero was detected
			if(Sensor->waitcount > 6 && Sensor->position != 0)
			{
				Sensor->currentstatus = STATE_WAIT_FOR_HIGH;
				Sensor->bitset[(Sensor->position)-1] = 0;
			}
			else
			Sensor->currentstatus = STATE_WAIT_FOR_START;
		}
		//If the start bit has been detected
		if(Sensor->waitcount > 22 && Sensor->position == 0)
		{
			Sensor->currentstatus = STATE_WAIT_FOR_HIGH;
		}
		//if a one has been detected
		else if(Sensor->waitcount > 14 && Sensor->position != 0)
		{
			Sensor->currentstatus = STATE_WAIT_FOR_HIGH;
			Sensor->bitset[Sensor->position-1] = 1;
		}

		break;

		case STATE_WAIT_FOR_HIGH:
		if(_RD3 == 1)
		{
			Sensor->waitcount = 0;
			Sensor->currentstatus = STATE_SETTLE_ON_HIGH;
		}
		break;
		case STATE_SETTLE_ON_HIGH:
		Sensor->waitcount++;
		if(Sensor->waitcount >= 2)
		{
			if( _RD3 == 1)
			Sensor->currentstatus = STATE_WAIT_FOR_LOW;
			else
			Sensor->currentstatus = STATE_WAIT_FOR_START;
		}
		break;
		case STATE_WAIT_FOR_LOW:

		if(_RD3 == 0)
		{
			if(Sensor->position >= 4)
			Sensor->currentstatus = STATE_WAIT_FOR_START;
			else
			{
				Sensor->position++;

				Sensor->waitcount = 0;
				Sensor->currentstatus = STATE_SETTLE_ON_LOW;
			}
		}
		break;
	}
}