Introduction: Infrared Proximity Sensing Coffee Table Module & Color Changing Glowing Faucet
This video can be found on youtube.
http://www.youtube.com/watch?v=h5n0rw8wo14
Check out the other one, and some other of my videos
This is merely an instructable to explain how this device operates. I hope everything is not too obfuscated.
This prototype consists of three 8x8” modules. Each module operates independently of each other. Each module consists of 4 “pixels”. Each pixel is 4 inches square and consists of 21 LEDs, two IR emitting diodes, and two IR photodiodes. The mode of sensing is active infrared.
Check out my Color Changing Glowing Faucet
http://www.youtube.com/watch?v=mBzTNcXIbWA
Step 1: Infrared Sensor
V-in is 5 volts.
Step 2: IR Emitters
Two IR emitters are connected in series per pixel. The supply voltage is 5 volts. The rest of the voltage is dropped by a small 10 ohm resistor to regulate the high current flow. The IR emitters and IR photodiodes are connected to the 5V supply. Whenever the IR emitters are turned on, a small amount of current is actually pulled away from the IR photodiode voltage divider arrangement. The photodiodes being reversed biased allows this to happen. This reverse flow creates a dip in the voltage level at the node where the ADC reading is taken. I got rid of this dip by putting a small value capacitor (47pF – 100 pF) across the 1Mohm resistor of the voltage divider. This greatly reduces the level of dip, but also costs time to charge up the capacitor when the voltage level rises quickly. All of this is easily seen with an oscilloscope. I have attached snap shots of my screen oscilloscope reading. This is the source of requiring a software delay, to allow time for the voltage level to reach its peak at the sampled node of the voltage divider.
Step 3: The Code
/***
PIN ASSIGNMENTS ON ATMEGA48
PC6 (PCINT14/RESET)
PC5 (ADC5/SCL/PCINT13) // I2C Clock input
PC4 (ADC4/SDA/PCINT12) // I2C Data input
PC3 (ADC3/PCINT11) //Sensor 4 IR Receiver
PC2 (ADC2/PCINT10) //Sensor 3 IR Receiver
PC1 (ADC1/PCINT9) //Sensor 2 IR Receiver
PC0 (ADC0/PCINT8) //Sensor 1 IR Receiver
PB7 (PCINT7/XTAL2/TOSC2) //IR 4 Trigger
PB6 (PCINT6/XTAL1/TOSC1) //IR 3 Trigger
PB5 (SCK/PCINT5) //IR 2 Trigger
PB4 (MISO/PCINT4) //IR 1 Trigger
PB3 (MOSI/OC2A/PCINT3) //PWM 3
PB2 (SS/OC1B/PCINT2)
PB1 (OC1A/PCINT1)
PB0 (PCINT0/CLKO/ICP1)
PD0 (PCINT16/RXD)
PD1 (PCINT17/TXD)
PD2 (PCINT18/INT0)
PD3 (PCINT19/OC2B/INT1) //PWM 4
PD4 (PCINT20/XCK/T0)
PD5 (PCINT21/OC0B/T1) //PWM 2
PD6 (PCINT22/OC0A/AIN0) //PWM 1
PD7 (PCINT23/AIN1)
***/
#define IR_1_ON PORTB |= (1<<4)
#define IR_2_ON PORTB |= (1<<5)
#define IR_3_ON PORTB |= (1<<6)
#define IR_4_ON PORTB |= (1<<7)
#define IR_1_OFF PORTB &= ~(1<<4)
#define IR_2_OFF PORTB &= ~(1<<5)
#define IR_3_OFF PORTB &= ~(1<<6)
#define IR_4_OFF PORTB &= ~(1<<7)
#define PWM1 6 //PORTD PWM pin assignments
#define PWM2 5 //PORTD
#define PWM3 3 //PORTB
#define PWM4 3 //PORTD
#define F_CPU 8000000UL
#include
#include
#include
//#include
/****Function Declarations****/
int ADC_read(void);
void A2D_Channel_Select(unsigned char channel);
void Init_ADC(void);
void Init_Timer0(void);
void Init_Timer1(void);
void Init_Timer2(void);
void Delay(void);
void Calibrate_Sensors(void);
//void Init_I2C_Slave_Rx(void);
All, but one, of these variables is declared volatile because basically all of the work is done in
interrupt service routines
/****Global Variable Declarations****/
volatile char Sensor_Values_Updated = 0;
volatile char Timer1_Overflow = 0;
volatile unsigned char channel = 0;
volatile int Amb_Sensor_1 = 0, Amb_Sensor_2 = 0, Amb_Sensor_3 = 0, Amb_Sensor_4 = 0;
volatile int Sensor_1 = 0, Sensor_2 = 0, Sensor_3 = 0, Sensor_4 = 0;
volatile int Initial_1 = 0, Initial_2 = 0, Initial_3 = 0, Initial_4 = 0;
volatile int New_PWM1 = 0, New_PWM2 = 0, New_PWM3 = 0, New_PWM4 = 0;
volatile int Old_PWM1 = 0, Old_PWM2 = 0, Old_PWM3 = 0, Old_PWM4 = 0;
unsigned char buffer = 8;
int main(void)
{
DDRB = 0xff;
//make sure IR emitters are turned off, and PWM 3
PORTB &= ~((1 << 7)|(1 << 6)|(1 << 5)|(1 << 4)|(1 << 3));
DDRC = 0x00; //make PORT C inputs
DDRD = 0xff;
PORTD = 0x00; //set all of PORT D low. ensures
Init_ADC();
sei();
Calibrate_Sensors();
PORTD |= (1 << PWM1); //blink to indicate end of Calibration
_delay_ms(600);
PORTD &= ~(1 << PWM1);
Init_Timer0();
Init_Timer2();
//Init_I2C_Slave_Rx();
while(1)
{
//do something?
//. . .
}
}
With the clock running at roughly 8MHz, and Timer 1 counting up to 65535. The timer will overflow roughly 122 times a second. This ISR will fire and the timer overflow variable will increment, and then the SWITCH/CASE function will choose the next pixel to test
ISR(TIMER1_OVF_vect)
{
Timer1_Overflow++; //increment timer overflow variable
switch(Timer1_Overflow)
{
case 1:
A2D_Channel_Select(0); //select ADC channel 0
Amb_Sensor_1 = ADC_read(); //take ambient IR sensor reading
IR_1_ON; //turn on IR 1 LED, PORTB |= (1<<4)
Delay(); //delay for the IR receiver to settle
Sensor_1 = ADC_read(); //take active ADC reading of IR receiver
IR_1_OFF; //turn off IR 1 LED
New_PWM1 = (Sensor_1 - Amb_Sensor_1) - Initial_1; //condition readings
if(New_PWM1 <= 0) { New_PWM1 = 0; } //prevent negative numbers
New_PWM1 = ((7*Old_PWM1)>>3) + (New_PWM1>>3);
if(OCR0A >= 1) {DDRD |= (1 << PWM1);}
else { DDRD &= ~(1 << PWM1); } //turn off LEDs completely
New_PWM1 <<= 2;
if(New_PWM1 > 255) { New_PWM1 = 255; }
OCR0A = New_PWM1;
New_PWM1 >>= 2;
The below code that is entirely commented out is a different brightness algorithm. It is a triggering algorithm that will fade the LEDs on when something comes within a threshold. And the LEDs will fade out slowly when the object is out of the threshold distance. This is useful because the operation could be more reliable and the fade out time can be adjusted to be very long or however long you want it. I haven't tested this code so I am not sure if it will work 100%
/***** //Trigger sequence
if(New_PWM1 > Initial_1)
{
DDRD |= (1 << PWM1);
if(OCR0A < 255)
{
OCR0A += (255 - OCR0A)>>2 ;
//OCR0A++;
}
if (New_PWM1 < (Initial_1 + 8))
{
Initial_1 = ((7*Initial_1)>>3) + (New_PWM1>>3);
}
}
else if(New_PWM1 < Initial_1)
{
if(OCR0A > 0)
{
OCR0A -= (OCR0A >> 4)+1;
//OCR0A--;
}
else if(OCR0A <= 0)
{
DDRD &= ~(1 << PWM1);
}
}
*****/
Old_PWM1 = New_PWM1;
break;
case 2:
A2D_Channel_Select(1); //select ADC channel 1
Amb_Sensor_2 = ADC_read();
IR_2_ON; //turn on IR 2 LED, PORTB |= (1<<5)
Delay(); //delay for the IR receiver to settle
Sensor_2 = ADC_read(); //take ADC reading
IR_2_OFF; //turn off IR 2 LED
New_PWM2 = (Sensor_2 - Amb_Sensor_2) - Initial_2;
if(New_PWM2 < 0) { New_PWM2 = 0; }
New_PWM2 = ((7*Old_PWM2)>>3) + (New_PWM2>>3);
if(OCR0B >= 1) {DDRD |= (1 << PWM2);}
else { DDRD &= ~(1 << PWM2); }
New_PWM2 <<= 2;
if(New_PWM2 > 255) { New_PWM2 = 255; }
OCR0B = New_PWM2;
New_PWM2 >>= 2;
/*
if(New_PWM2 > Initial_2)
{
DDRD |= (1 << PWM2);
if(OCR0B < 255)
{
OCR0B += (255 - OCR0B)>>2 ;
//OCR0B++;
}
if (New_PWM2 < (Initial_2 + 8))
{
Initial_2 = ((7*Initial_2)>>3) + (New_PWM2>>3);
}
}
else if(New_PWM2 < Initial_2)
{
if(OCR0B > 0)
{
OCR0B -= (OCR0B >> 4)+1;
//OCR0B--;
}
else if(OCR0B <= 0)
{
DDRD &= ~(1 << PWM2);
}
}
*/
Old_PWM2 = New_PWM2;
break;
case 3:
A2D_Channel_Select(2); //select ADC channel 2
Amb_Sensor_3 = ADC_read();
IR_3_ON; //turn on IR 3 LED, PORTB |= (1<<6)
Delay(); //delay for the IR receiver to settle
Sensor_3 = ADC_read(); //take ADC reading
IR_3_OFF; //turn off IR 3 LED
New_PWM3 = (Sensor_3 - Amb_Sensor_3) - Initial_3;
if(New_PWM3 < 0) { New_PWM3 = 0; }
New_PWM3 = ((7*Old_PWM3)>>3) + (New_PWM3>>3);
if(OCR2A >= 1) {DDRB |= (1 << PWM3);}
else { DDRB &= ~(1 << PWM3); }
New_PWM3 <<= 2;
if(New_PWM3 > 255) { New_PWM3 = 255; }
OCR2A = New_PWM3;
New_PWM3 >>= 2;
/*
if(New_PWM3 > Initial_3)
{
DDRB |= (1 << PWM3);
if(OCR2A < 255)
{
OCR2A += (255 - OCR2A)>>2 ;
//OCR2A++;
}
if (New_PWM3 < (Initial_3 + 8))
{
Initial_3 = ((7*Initial_3)>>3) + (New_PWM3>>3);
}
}
else if(New_PWM3 < Initial_3)
{
if(OCR2A > 0)
{
OCR2A -= (OCR2A >> 4)+1;
//OCR2A--;
}
else if(OCR2A <= 0)
{
DDRB &= ~(1 << PWM3);
}
}
*/
Old_PWM3 = New_PWM3;
break;
case 4:
A2D_Channel_Select(3); //select ADC channel 3
Amb_Sensor_4 = ADC_read();
IR_4_ON; //turn on IR 4 LED, PORTB |= (1<<7)
Delay(); //delay for the IR receiver to settle
Sensor_4 = ADC_read(); //take ADC reading
IR_4_OFF; //turn off IR 4 LED
New_PWM4 = (Sensor_4 - Amb_Sensor_4) - Initial_4;
if(New_PWM4 < 0) { New_PWM4 = 0; }
New_PWM4 = ((7*Old_PWM4)>>3) + (New_PWM4>>3);
if(OCR2B >= 1) {DDRD |= (1 << PWM4);}
else { DDRD &= ~(1 << PWM4); }
New_PWM4 <<= 2;
if(New_PWM4 > 255) { New_PWM4 = 255; }
OCR2B = New_PWM4;
New_PWM4 >>= 2;
/*
if(New_PWM4 > Initial_4)
{
DDRD |= (1 << PWM4);
if(OCR2B < 255)
{
OCR2B += (255 - OCR2B)>>2 ;
//OCR2B++;
}
if (New_PWM4 < (Initial_4 + 8))
{
Initial_4 = ((7*Initial_4)>>3) + (New_PWM4>>3);
}
}
else if(New_PWM1 < Initial_4)
{
if(OCR2B > 0)
{
OCR2B -= (OCR2B >> 4)+1;
//OCR2B--;
}
else if(OCR2B <= 0)
{
DDRD &= ~(1 << PWM4);
}
}
*/
Old_PWM4 = New_PWM4;
Timer1_Overflow = 0; //reset
Sensor_Values_Updated = 1; //new values ready
break;
}//end switch
}//end ISR
This is something I am going to try and figure out later. It is untested elementary code which could allow me to use the Two Wire Interface (I2C) so several controllers and communicate with each other or have one master and a bunch of slaves.
/****
ISR(TWI_vect) //to include later when I get this figured out
{
switch(TWSR)
{
case TW_SR_SLA_ACK: //0x60 //Own address Rx
Byte_Number == 1;
break;
case TW_SR_DATA_ACK: // 0x80 , data in TWDR
switch(Byte_Number)
{
case 1:
Reg_Addr = TWDR;
Byte_Number++;
break;
case 2:
Reg_Val = TWDR;
Byte_Number = 0; //reset, unless more bytes are coming
break;
case Max_Bytes_Expected:
Reg_Val = TWDR;
Byte_Number = 0; //reset, unless more bytes are coming
break;
}
break;
case TW_SR_GCALL_DATA_ACK: // 0x90
if(Byte_Number == 1)
{
Reg_Addr = TWDR;
Byte_Number++;
}
else if(Byte_Number == 2)
{
Reg_Val = TWDR;
Byte_Number = 0; //reset, unless more bytes are coming
}
break;
}//end switch
}//end ISR
void Init_I2C_Slave_Rx(void)
{
//Set Device Address in TWAR
TWAR = 10; //maybe make this as an argument to this function
TWCR |= ((1 << TWEA)|(1 << TWEN));
TWCR &= ~((1 << TWSTA)|(1 << TWSTO));
}
****/
void Calibrate_Sensors(void) //establish initial ambient sensor values
{
char q = 0;
Init_Timer1();
for(q=0; q<32; q++) //should take one second-ish
{
//wait for Sensor cycle to be done, then gather sensors values
while(Sensor_Values_Updated == 0) {}
Initial_1 += (Sensor_1 - Amb_Sensor_1); //initial difference
Initial_2 += (Sensor_2 - Amb_Sensor_2);
Initial_3 += (Sensor_3 - Amb_Sensor_3);
Initial_4 += (Sensor_4 - Amb_Sensor_4);
Sensor_Values_Updated = 0; //reset
}//end for
Initial_1 = (Initial_1 >> 5) + buffer;
Initial_2 = (Initial_2 >> 5) + buffer;
Initial_3 = (Initial_3 >> 5) + buffer;
Initial_4 = (Initial_4 >> 5) + buffer;
}
void Init_ADC(void)
{
ADMUX |= 1 << REFS0; //AVCC with external capacitor at AREF pin
ADMUX |= (1<
}
void Init_Timer0(void) //PWM for sensors 1 & 2
{
//Fast PWM, non-inverting, WGM02-WGM00 == 011, no overflow interrupt
TCCR0A |= ((1 << COM0A1)|(1 << COM0B1)|(1 << WGM01)|(1 << WGM00));
TCCR0B |= (1 << CS00); //start clock, no prescale
}
void Init_Timer1(void)
{
//no PWM, enable overflow interrupt,
//TOP == 0xFFFF == 65536 cycles == roughly 122 overflow interrupts/sec
TCCR1B |= (1 << CS10);
TIMSK1 |= (1 << TOIE1);
}
void Init_Timer2(void) //PWM for sensors 3 & 4
{
//Fast PWM, non-inverting, WGM22-WGM20 == 011, no overflow interrupt
TCCR2A |= ((1 << COM2A1)|(1 << COM2B1)|(1 << WGM21)|(1 << WGM20));
TCCR2B |= (1 << CS20); //start clock, no prescale
}
int ADC_read(void) /***select ADC channel prior to calling this function***/
{
int ADC_value = 0;
int ADCsample;
char i;
ADCSRA |= (1< ADCSRA |= (1< while ((ADCSRA & ADSC)); //Wait for conversion to complete, and forget about it
//this is done no more than 64 times,
for (i=0; i<64; i++)
{
ADCSRA |= (1< while ((ADCSRA & ADSC)); //wait for conversion to finish
ADCsample = ADCH;
//ADCsample += (ADCH<<8); //Left shift the top two bits 8 places
ADC_value += ADCsample; //add ADCsample to ADC_sensor
}
ADC_value = (ADC_value >> 6);
return ADC_value;
ADCSRA &= ~(1<
void A2D_Channel_Select(unsigned char channel)
{
switch (channel)
{
case 0: //select A2D channel 0
ADMUX &= ~((1 << 3)|(1 << 2)|(1 << 1)|(1 << 0));
break;
case 1: //select A2D channel 1
ADMUX &= ~((1 << 3)|(1 << 2)|(1 << 1));
ADMUX |= (1 << 0);
break;
case 2: //select A2D channel 2
ADMUX &= ~((1 << 3)|(1 << 2)|(1 << 0));
ADMUX |= (1 << 1);
break;
case 3: //select A2D channel 3
ADMUX &= ~((1 << 3)|(1 << 2));
ADMUX |= ((1 << 1)|(1 << 0));
break;
/* I am not using these for this project
case 4: //select A2D channel 4
ADMUX &= ~((1 << 3)|(1 << 1)|(1 << 0));
ADMUX |= (1 << 2);
break;
case 5: //select A2D channel 5
ADMUX &= ~((1 << 3)|(1 << 1));
ADMUX |= ((1 << 2)|(1 << 0));
break;
*/
}//end switch
}
void Delay(void)
{
_delay_us(100);
}
Step 4: The Circuit
Circuit schematic, all of the components were drawn and IC pins were labeled or the IC has the effective internal circuit drawn in it to illustrate what is going on inside the IC. This also does not include the 6 prong connection for the programmer that is wired to pins on the uC.
Circuit build design, for the size perf board that I had, this is the drawing that tries to show how ICs were arranged and connections were routed. The idea is that the solid lines are wires, and dotted lines are soldered connections. The dotted lines try to trace only vertical and horizontal lines and follow solder points, but it is not perfect.
Step 5: More Pictures
Step 6: Parts List
I get most of my parts from mouser.com
I have them listed as:
Mouser Part Number
Item/Manufacturer
Slight Description
556-ATMEGA48-20PU
Atmel Microcontrollers (MCU)
4kB Flash 0.256kB
638-IR204-A
Everlight Infrared Emitters
Infrared LED 940nm
512-QSD2030F
Fairchild Photodiodes
PIN PHOTODIODE
757-ULN2803APG(5,M)
Toshiba Darlington Transistors
8CH. 50V/.5A IFD IC
595-TLC274CN
TI Op Amps
Quad LiMCMOS
511-L7805CV
ST Linear Regulators - Standard
5.0V 1.0A Positive
571-1-390261-3
Tyco IC & Component Sockets
14P ECONOMY TIN
571-1-390261-5
Tyco IC & Component Sockets
18P ECONOMY TIN
571-1-390261-9
Tyco IC & Component Sockets
28P ECONOMY TIN SKT
I got the perf board off of
http://www.allelectronics.com/make-a-store/category/455/Perf-Boards/1.html
I got the 24v power supply off of ebay