So I decided to emulate Phillips Ambilight technology for my PC and wanted to post up info about it here for other peoples reference.
Here is a pretty terrible video of it in action. Its reading the color from the center of the screen area. You can only really see it change with the flag at the end, but it looks better in real life.
Here is a pretty terrible video of it in action. Its reading the color from the center of the screen area. You can only really see it change with the flag at the end, but it looks better in real life.
First I would like to thanks these three pages for getting me started and for helping with some technical issues
Second, I am not a great coder so I'm sure the code could be a lot nicer, but I learned a lot.
Code Breakdown:
1. Arduino turns on and waits for any byte of serial data to come in to basically know when its connected.
2. VB.net program starts, connectes to Arduino, and sends character to tell Arduino that its connected
3. Arduino steps into program, sends "R" to VB.net program to tell it to send data (could be any character, but has to be same in VB code).
4.VB.net is constantly getting the average color vale of the screen area specified with timer tick.
5. When VB.net program sees the R on the serial line it prints out the 3 PWM R,G,&B values.
Tricks:
I wanted this code to be simple and use the least amount of data to send the PWM values. So since serial data is sent as character strings, I convert the PWM RGB values to whatever character corresponds to that. For example if the red value is 65 that will be converted to the character A so that the VB.net serial data is sent out as A instead of 6 and then 5.
Since I think the Arduino uses ASCII values from -127 to 127 I divide my 8bit PWM value by 2 in the VB.net program before sending it since the RGB values are between 0 and 255. My Arduino code then multiplies that by 2 to get the original value back.
Arduino must be reset before the VB program is started. Then the VB program must be readingcolor values before the com port is opened.
I will comment the code more when I get time. I was getting memory exception in VB due to the getDC call. Make sure if you use it to release it with ReleaseDC. Also check the return value of ReleaseDC to make sure it is 1, anything else means its not releasing. Most of the code examples I found didnt call ReleaseDC which makes the program eat memory. I'm not sure of how I got the pointer stuff working in the call, but it does seem to work and that pointer is necessary.
Wiring
Uses default USB to serial connection to Arduino
LED is tricolor with common cathode so arduino sinks current. Red ground is connected to pin 9, green to 10, and blue to 11. These are PWM pins. Common pin is connected to +5 from arduino. Resistors are used to limit current to specified values. 50 ohm for green and blue, 60 ohm for red.
What needs work:
- Add multiplexing to control for 3 regions of screen color
- Reduce CPU usage of VB.net program. Taking all those screen shots eats up the CPU. It would be nice to lower the timer value to sample more quickly.
- Comment and clean up everything
- The averaging code is pretty sloppy and doesn't actually get a great average, but it works. Values seem to always be between 50 and 205 which reduces the color range.
- Make a nice box to house it all in.
- Add some pics to this post.
- Make it work for videos. I think it should work for everything except hardware overlay stuff. Video games might work, I have tested it with a couple. Flash videos like youtube or hulu work with it.
Arduino Code:
byte colorVal;
byte colorValR;
byte colorValG;
byte colorValB;
int redPin = 9; // Red LED, connected to digital pin 9
int greenPin = 10; // Green LED, connected to digital pin 10
int bluePin = 11; // Blue LED, connected to digital pin 11
void setup() {
pinMode(redPin, OUTPUT); // sets the pins as output
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
Serial.begin(19200);
analogWrite(redPin, 0); // set them all to mid brightness
analogWrite(greenPin, 75); // set them all to mid brightness (scaled for red, to look white)
analogWrite(bluePin, 0); // set them all to mid brightness
colorValR = 0;
colorValG = 0;
colorValB = 0;
while(!Serial.available()){ //Wait for PC to connect before moving on
}
colorVal = Serial.read();
}
void loop () {
Serial.print('R'); //Sends R to VB.net to say "I am ready for data"
while(!Serial.available()){ //Waits in this loop until serial buffer has data
}
colorVal = Serial.read(); //reads byte from serial, colorVal is a byte, not char, so its some number
colorValR = colorVal * 2; //sets value for red led (multiplied by 2 since sent as ASCII for 1/2 the value)
while(!Serial.available()){
}
colorVal = Serial.read();
colorValG = colorVal * 2;
while(!Serial.available()){
}
colorVal = Serial.read();
colorValB = colorVal * 2;
analogWrite(redPin, 255-(int)(colorValR*.7)); //turns on all the LED's with PWM values recieved. Since my RED LED is brighter I scale it so that RGB fully on looks white and not red. Also LED has a common cathode so 0 is fully on 255 is fully off and thats why it is 255 - val
analogWrite(greenPin, 255-colorValG);
analogWrite(bluePin, 255-colorValB);
}
VB.net Code:
These are all the variables that I placed in a module so I can access them from anywhere
Public Module Module1
Public rave As Byte
Public gave As Byte
Public bave As Byte
Public raveS As Char
Public gaveS As Char
Public baveS As Char
Public a As Integer
Public b As Integer
Public strRgb As String
Public lColor As Integer
Public hdc As Integer
Public hdccheck As IntPtr
Public serialinval As Char
Public intX As Integer = Screen.PrimaryScreen.Bounds.Width
Public intY As Integer = Screen.PrimaryScreen.Bounds.Height
End Module
This is the code in the main form
Option Strict On
Option Explicit On
Imports VB = Microsoft.VisualBasic
Imports System.Runtime.InteropServices
Imports System.Console
Friend Class Form1
Inherits System.Windows.Forms.Form
Private Declare Function GetDC Lib "user32" Alias "GetDC" (ByVal hwnd As Integer) As Integer
Declare Function ReleaseDC Lib "user32" (ByVal hwnd As IntPtr, ByVal hdc As IntPtr) As IntPtr
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Integer, ByVal x As Integer, ByVal y As Integer) As Integer
Private Sub Form1_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load
Timer1.Interval = 75
Timer1.Enabled = False
For i As Integer = 0 To My.Computer.Ports.SerialPortNames.Count - 1
portlist.Items.Add(My.Computer.Ports.SerialPortNames(i))
Next
End Sub
Private Sub Timer1_Tick(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles Timer1.Tick
rave = 127 'Set a middle color to start as the fudged average
gave = 127
bave = 127
a = 550
b = 350
hdc = GetDC(0)
While a <>
While b <>
lColor = GetPixel(hdc, a, b)
strRgb = VB.Right("000000" & Hex(lColor), 6)
Dim rval As Byte = Convert.ToByte(VB.Right(strRgb, 2), 16)
Dim gval As Byte = Convert.ToByte(Mid(strRgb, 3, 2), 16)
Dim bval As Byte = Convert.ToByte(VB.Left(strRgb, 2), 16)
rave = CByte((rave * 100 + rval) / 101)
gave = CByte((gave * 100 + gval) / 101)
bave = CByte((bave * 100 + bval) / 101)
b = b + 20 '50
End While
b = 0
a = a + 20 '50
End While
ReleaseDC(CType(0, IntPtr), CType(hdc, IntPtr))
raveS = Chr(CByte(rave / 2))
gaveS = Chr(CByte(gave / 2))
baveS = Chr(CByte(bave / 2))
If SerialPort1.IsOpen Then
serialinval = ChrW(SerialPort1.ReadByte())
If serialinval = "R" Then
SerialPort1.Write(raveS)
SerialPort1.Write(gaveS)
SerialPort1.Write(baveS)
End If
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Timer1.Enabled = True Then
Timer1.Enabled = False
Button1.Text = "Start"
Else
Timer1.Enabled = True
Button1.Text = "Stop"
End If
End Sub
Private Sub ConnectBut_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ConnectBut.Click
If SerialPort1.IsOpen Then
SerialPort1.Close()
ConnectBut.Text = "Connect"
ElseIf portlist.Text = "Choose Port" Then
MsgBox("Choose A Port", MsgBoxStyle.OkOnly )
Else
With SerialPort1
.PortName = portlist.Text
.BaudRate = 19200
.Parity = IO.Ports.Parity.None
.DataBits = 8
.StopBits = IO.Ports.StopBits.One
End With
SerialPort1.Open()
SerialPort1.Write("a")
ConnectBut.Text = "Disconnect"
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Form2.Show()
End Sub
End Class
This code is for form 2 which shows the hex RGB value as well as the decimal values. The background color changes based on the value so you can check for color accuracy.
Public Class Form2
Private Sub Form2_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
Timer1.Enabled = False
End Sub
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Timer1.Interval = 200
Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
TextBox1.Text = strRgb
TextBox3.Text = CStr(rave)
TextBox4.Text = CStr(gave)
TextBox5.Text = CStr(bave)
BackColor = Color.FromArgb(255, rave, gave, bave)
End Sub
End Class