12.31.2008

Arduino vb.net control

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. 

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