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