Building a color camera with an IR controlled RGB LED lamp and a monochromatic camera

“Birgit, you are the only person who manages to crash the software of a freaking lamp.” – whoopsie;-)

So my dad just bought one of those (rather pointless, but.. uh, well) IR controlled RGB lamps, and being both nerds, we decided to spend Christmas Eve (okay, okay) hacking the remote control.

(Here’s a link to the lamp we used)

There are a bazillion cheap Chinese lamps and RGB controllers out there, so it’s pretty pointless to try any of the existing Arduino projects. Anyway, I wanted to try Dads’ beloved toolchain (Arduino programmed with AVR Studio, which is expensive, but nice)..

But to do that, we first had to decode the IR protocol, and for that, we built a receiver with a DAQ USB6008 and a cheap 38Khz-IR-receiver..

That receiver is extremely simple:

Receiving IR signal

(The lower displays are for testing my TV remote.. ;-))

Anyways, you find out that the signal looks like this:

Signal from IR remote

You need to reproduce this signal with an Arduino and a simple IR red. (Remember, you can’t see the 38kHz, but you need to modulate this.)

Three pieces of information I wish I had in the beginning:

1) At the start, you need an idle phase. If you don’t send that one, you lose.

2) Signal= 1 Byte “all 0s”, 1 Byte “all 1s”; 1 Byte “Command”, 1 Byte “Inverse Command”

3) 0/1 are distinguished by the ratio between high/low.

This codec is bascially something like the NEC protocol: http://www.sbprojects.com/knowledge/ir/nec.php

I’m going to attach how we wrote a very simple (and very messy) code that turns the lamp red.

The command for RED is 0010 0000, the command for OFF is 0100 0000.

As a next step, I wanted to take a “sweep” and just go through the 256 values and see what happens (maybe some secret behaviour?). However, the lamp stopped reacting after an erronous command and wouldn’t recover until -tada – we turned it on/off again.

Part of me is truly curious. Part of me wants to build a setup with automated testing of commands : Send command, send “RED”, send “WHITE”, use a webcam and IMAQ vision to see if it crashed or did something interesting, if not, command a relay to restart the lamp … the other part of me realises that this is probably reserved for “after Christmas” 🙂

——


/*******************************************************
This program was created by the
CodeWizardAVR V3.03 Advanced
Automatic Program Generator
© Copyright 1998-2013 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com


Project :
Version :
Date : 24.12.2013
Author :
Company :
Comments:


Chip type : ATmega328P
Program type : Application
AVR Core Clock frequency: 16,000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 512
*******************************************************/

#define ZEIT 700*76/81
// 656
//int sig[33];

int sig[] = { 0,
0,0,0,0,0,0,0,0,
1,1,1,1,0,1,1,1,
0,0,1,0,0,0,0,0, //ROT
1,1,0,1,1,1,1,1};

int sig1[] = { 0,
0,0,0,0,0,0,0,0,
1,1,1,1,0,1,1,1,
0,1,0,0,0,0,0,0, //OFF
1,0,1,1,1,1,1,1};

int sig2[] = { 0,
0,0,0,0,0,0,0,0,
1,1,1,1,0,1,1,1,
1,1,0,0,0,0,0,0, //ON
0,0,1,1,1,1,1,1};

void main(void)
{ int i;
// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=(1<<CLKPCE);
CLKPR=(0<<CLKPCE) | (0<<CLKPS3) | (0<<CLKPS2) | (0<<CLKPS1) | (0<<CLKPS0);
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif

// Input/Output Ports initialization
// Port B initialization
// Function: Bit7=In Bit6=In Bit5=In Bit4=In Bit3=In Bit2=In Bit1=In Bit0=In
DDRB=(0<<DDB7) | (0<<DDB6) | (0<<DDB5) | (0<<DDB4) | (0<<DDB3) | (0<<DDB2) | (0<<DDB1) | (0<<DDB0);
// State: Bit7=P Bit6=P Bit5=P Bit4=P Bit3=P Bit2=P Bit1=P Bit0=P
PORTB=(1<<PORTB7) | (1<<PORTB6) | (1<<PORTB5) | (1<<PORTB4) | (1<<PORTB3) | (1<<PORTB2) | (1<<PORTB1) | (1<<PORTB0);


// Port C initialization
// Function: Bit6=In Bit5=In Bit4=In Bit3=In Bit2=In Bit1=In Bit0=In
DDRC=(0<<DDC6) | (0<<DDC5) | (0<<DDC4) | (0<<DDC3) | (0<<DDC2) | (0<<DDC1) | (0<<DDC0);
// State: Bit6=T Bit5=T Bit4=T Bit3=T Bit2=T Bit1=T Bit0=T
PORTC=(0<<PORTC6) | (0<<PORTC5) | (0<<PORTC4) | (0<<PORTC3) | (0<<PORTC2) | (0<<PORTC1) | (0<<PORTC0);

// Port D initialization
// Function: Bit7=In Bit6=Out Bit5=In Bit4=In Bit3=Out Bit2=In Bit1=In Bit0=In
DDRD=(0<<DDD7) | (1<<DDD6) | (0<<DDD5) | (0<<DDD4) | (1<<DDD3) | (0<<DDD2) | (0<<DDD1) | (0<<DDD0);
// State: Bit7=T Bit6=0 Bit5=T Bit4=T Bit3=0 Bit2=T Bit1=T Bit0=T
PORTD=(0<<PORTD7) | (0<<PORTD6) | (0<<PORTD5) | (0<<PORTD4) | (0<<PORTD3) | (0<<PORTD2) | (0<<PORTD1) | (0<<PORTD0);

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 16000,000 kHz
// Mode: CTC top=OCR0A
// OC0A output: Toggle on compare match
// OC0B output: Disconnected
// Timer Period: 0,01275 ms
// Output Pulse(s):
// OC0A Period: 0,0255 ms Width: 0,01275 ms
TCCR0A=(0<<COM0A1) | (1<<COM0A0) | (0<<COM0B1) | (0<<COM0B0) | (1<<WGM01) | (0<<WGM00);
TCCR0B=(0<<WGM02) | (0<<CS02) | (0<<CS01) | (1<<CS00);
TCNT0=0x00;
OCR0A=0xC7;//0xCB;
OCR0B=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer1 Stopped
// Mode: Normal top=0xFFFF
// OC1A output: Disconnected
// OC1B output: Disconnected
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=(0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (0<<WGM11) | (0<<WGM10);
TCCR1B=(0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (0<<WGM12) | (0<<CS12) | (0<<CS11) | (0<<CS10);
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer2 Stopped
// Mode: Normal top=0xFF
// OC2A output: Disconnected
// OC2B output: Disconnected
ASSR=(0<<EXCLK) | (0<<AS2);
TCCR2A=(0<<COM2A1) | (0<<COM2A0) | (0<<COM2B1) | (0<<COM2B0) | (0<<WGM21) | (0<<WGM20);
TCCR2B=(0<<WGM22) | (0<<CS22) | (0<<CS21) | (0<<CS20);
TCNT2=0x00;
OCR2A=0x00;
OCR2B=0x00;

// Timer/Counter 0 Interrupt(s) initialization
TIMSK0=(0<<OCIE0B) | (0<<OCIE0A) | (0<<TOIE0);

// Timer/Counter 1 Interrupt(s) initialization
TIMSK1=(0<<ICIE1) | (0<<OCIE1B) | (0<<OCIE1A) | (0<<TOIE1);

// Timer/Counter 2 Interrupt(s) initialization
TIMSK2=(0<<OCIE2B) | (0<<OCIE2A) | (0<<TOIE2);

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// Interrupt on any change on pins PCINT0-7: Off
// Interrupt on any change on pins PCINT8-14: Off
// Interrupt on any change on pins PCINT16-23: Off
EICRA=(0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (0<<ISC00);
EIMSK=(0<<INT1) | (0<<INT0);
PCICR=(0<<PCIE2) | (0<<PCIE1) | (0<<PCIE0);

// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART0 Mode: Asynchronous
// USART Baud Rate: 9600
UCSR0A=(0<<RXC0) | (0<<TXC0) | (0<<UDRE0) | (0<<FE0) | (0<<DOR0) | (0<<UPE0) | (0<<U2X0) | (0<<MPCM0);
UCSR0B=(0<<RXCIE0) | (0<<TXCIE0) | (0<<UDRIE0) | (1<<RXEN0) | (1<<TXEN0) | (0<<UCSZ02) | (0<<RXB80) | (0<<TXB80);
UCSR0C=(0<<UMSEL01) | (0<<UMSEL00) | (0<<UPM01) | (0<<UPM00) | (0<<USBS0) | (1<<UCSZ01) | (1<<UCSZ00) | (0<<UCPOL0);
UBRR0H=0x00;
UBRR0L=0x67;

// Analog Comparator initialization
// Analog Comparator: Off
// The Analog Comparator's positive input is
// connected to the AIN0 pin
// The Analog Comparator's negative input is
// connected to the AIN1 pin
ACSR=(1<<ACD) | (0<<ACBG) | (0<<ACO) | (0<<ACI) | (0<<ACIE) | (0<<ACIC) | (0<<ACIS1) | (0<<ACIS0);
ADCSRB=(0<<ACME);
// Digital input buffer on AIN0: On
// Digital input buffer on AIN1: On
DIDR1=(0<<AIN0D) | (0<<AIN1D);

// ADC initialization
// ADC disabled
ADCSRA=(0<<ADEN) | (0<<ADSC) | (0<<ADATE) | (0<<ADIF) | (0<<ADIE) | (0<<ADPS2) | (0<<ADPS1) | (0<<ADPS0);

// SPI initialization
// SPI disabled
SPCR=(0<<SPIE) | (0<<SPE) | (0<<DORD) | (0<<MSTR) | (0<<CPOL) | (0<<CPHA) | (0<<SPR1) | (0<<SPR0);

// TWI initialization
// TWI disabled
TWCR=(0<<TWEA) | (0<<TWSTA) | (0<<TWSTO) | (0<<TWEN) | (0<<TWIE);

PORTD.3 = 1;
delay_us(9900);
PORTD.3 = 0;
delay_us(3900);

for(i=0; i<33; i++)
{
if(sig[i] == 0)
{
PORTD.3 = 0;
delay_us((int)(ZEIT*1));
PORTD.3 = 1;
delay_us((int)(ZEIT*0.7));
}
else
{
PORTD.3 = 0;
delay_us((int)(ZEIT * 2.7));

PORTD.3 = 1;
delay_us((int)(ZEIT*0.7));
}
}
PORTD.3 = 0;

while (1)
{
}
}

So, with this I decided to do something useful and create a color camera from a monochromatic camera.

I took three pictures with it (it’s a direct show camera) – with red, blue and green light, and they look like this:

01

(By the way, a really good link that explains the data types and surfaces and stuff in CINDER: http://www.creativeapplications.net/tutorials/images-in-cinder-tutorials-cinder/)

Adding the pictures works like this:


gl::clear( Color( 0.0f, 0.0f, 0.0f ) );
gl::setMatricesWindow( getWindowWidth(), getWindowHeight() );

Surface8u red=loadImage(loadAsset( "01.png") );
Surface::Iter ired = red.getIter( getWindowBounds() );
while( ired.line() ) {
while( ired.pixel() ) {
ired.b() = 0;
ired.g() = 0;
}
}
Surface8u green=loadImage(loadAsset( "02.png") );
Surface::Iter igreen = green.getIter( getWindowBounds() );
while( igreen.line() ) {
while( igreen.pixel() ) {
igreen.b() = 0;
igreen.r() = 0;
}
}
Surface8u blue=loadImage(loadAsset( "03.png") );
Surface::Iter iblue = blue.getIter( getWindowBounds() );
while( iblue.line() ) {
while( iblue.pixel() ) {
iblue.r() = 0;
iblue.g() = 0;
}
}

Surface resultSurface = red.clone();
Surface::Iter iterResult = resultSurface.getIter( getWindowBounds() );
while( iterResult.line() && ired.line() && igreen.line() && iblue.line() ) {
while( iterResult.pixel() && ired.pixel() && igreen.pixel() && iblue.pixel() ) {
iterResult.r() = ired.r();
iterResult.g() = igreen.g()*0.8;
iterResult.b() = iblue.b()*0.9;
}
}


gl::Texture sTexture = gl::Texture( resultSurface );
if( sTexture ) {
glPushMatrix();
gl::draw( sTexture );
glPopMatrix();
}

Not the fanciest code (I wanted to look at the layers separately, so you see the leftovers of this) , but it delivers this:

colorpic

Not perfect at all – (partly due to the not so nice environment and because triggering the pic was done by hand, partly because I could’t be bothered to do whitebalancing), but it’s definitely doable.

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: