dcsimg
Skip to end of metadata
Go to start of metadata

Introduction Video

Features

Cree MC-E Red/Green/Blue/Neutral White LED (1w per color)

Wakefield Starboard LED Heat Sink

Ledil 26° Lens (±13°) for Cree MC-E LEDs

Pan / Tilt servos, each with 180° of movement

Four Diodes Inc. AL8805 1A constant current LED drivers

Microchip PIC24FJ16GA002 16-bit 32MHz microcontroller

UART interface at 115,200bps, 8N1 via two 10 position 2mm-pitch sockets spaced for an XBee (series 1 is preferred) or the PAN1555 Bluetooth Breakout board.

Windows GUI interface to control color, intensity, and position, as well as create custom patterns and sequences.

DMX-512 interface (coming soon to an EEWiki near you!)

Functional Description

The Moving RGB LED combines a 4x1W LED (red, green, blue and neutral white), optics, heat sink, four adjustable constant current controllers, two servo motors (pan and tilt), a microcontroller, three PCBs, and the wireless interface of your choice, into a compact and powerful moving RGBW light source.  The source is controllable via a Windows GUI, DMX512, our Joystick Remote (details forthcoming), or the wireless serial interface of your choice.  The socket accepts any Digi XBee radio or any radio using the same land pattern and pinout.  The only required connections to the XBee (or similar) are +3.3V (pin 1), Ground (pin 10), and UART data out (pin 2).

Communication

The Moving RGB LED currently accepts two packet types: position and color. The tilde [~] character denotes a position packet, and the asterisk [*] character denotes a color packet. Following the tilde, the system expects an 8-bit pan value, followed by an 8-bit tilt value. Following the asterisk, the system expects 8-bit dimming values for Red, Green, Blue, and White, in that order. Controllers may send either packet type at any time, and a continuous stream of packets is acceptable. The receiver implements a simple state machine to enter color [*] or movement [~] states, and resets to the start state if any other character is received. Should a packet of data become corrupted or incomplete, the state machine will reset.

Schematics

Because three separate PCBs exist, three schematics were created.

Left:

Center:

Right:

PCBs

The PCBs were fabricated by OSH Park

Mechanical Structure

From top to bottom, the Moving RGB LED is made of the LED optics, the LED, the LED heat sink, two spacers, three PCB's, two servos, several zip-ties and copious amounts of hot glue. The PCBs are configured in an up-side-down "U" shape, allowing the weight of the heat sink and PCBs to be supported from two points. One side of the up-side-down "U" is a mechanical connection to the tilt servo output shaft, and the opposing side slides around a bushing which is concentric to the output shaft. The PCBs are connected orthogonally with ten position 0.1" right-angle header pins soldered to both PCBs. These pins provide mechanical coupling, as well as ten electrical connections, between each of the two side PCBs and the center PCB.

Software

Embedded

The Moving RGB LED is controlled locally by the PIC24FJ16GA002. The PIC24 generates PWM signals to dim (modulate) the brightness of each LED, and to control each servo. The LED PWMs are operated by the hardware PWM generator, and the servo signals are generated in software via a timer interrupt. The communication state machine operates in a UART receive interrupt.


#include "p24FJ16GA002.h"

#define bool unsigned char

#define TRUE 1
#define FALSE 0

#define ON 1
#define OFF 0

#define DOWN 0
#define UP 1

#define COMM_QUEUE_SIZE 64

#define MAX_DUTY 256


#define RED	OC1RS
#define GREEN	OC2RS
#define BLUE	OC3RS
#define WHITE	OC4RS

#define PAN_PIN _LATB14
#define TILT_PIN _LATB15

#define MAX_PAN_SERVO 2420
#define MIN_PAN_SERVO 700

#define MAX_TILT_SERVO 2550
#define MIN_TILT_SERVO 600

unsigned int pan_servo = MIN_PAN_SERVO;
unsigned int tilt_servo = MIN_TILT_SERVO;

unsigned char data_received = 0;

_CONFIG1( FWDTEN_OFF & JTAGEN_OFF & BKBUG_OFF & GWRP_OFF & GCP_OFF & ICS_PGx1)
_CONFIG2( FNOSC_FRCPLL );

int main(void)
{
	SRbits.IPL = 7;	// Set CPU priority to 7 so that user interrupts are disabled
	
	RED = 0;
	GREEN = 0;
	BLUE = 0;
	WHITE = 0;
	
	_TRISB14 = 0; // Pan servo
	_TRISB15 = 0; // Tilt servo
	_TRISB3 = 0; // U1TX
	_TRISB8 = 0;
	_TRISB9 = 0;
	_TRISB10 = 0;
	_TRISB11 = 0;


	//Initialize the ADC
	_PCFG = 0xFFFF;	// All pins digital


	// IO Mapping
	// Define output channels

	_IOLOCK = 0;								// Unlock Pins

	//INPUTS
	_U1RXR = 2;									// RP2
	_U2RXR = 7;									// RP7
	_SDI1R = 5; 								// RP5

	//OUTPUTS From table 10-3
	_RP3R		= 3; 								// U1TX
	_RP6R		= 5;		 						// U2TX
	_RP9R		= 18; 							// OC1 Red
	_RP8R		= 19;								// OC2 Green
	_RP10R	= 20; 							// OC3 Blue
	_RP11R	= 21; 							// OC4 White
	_RP13R	= 8; 								// SCK1OUT
	_RP12R	= 7;	 							// SDO1

	_IOLOCK = 1;								// Lock Pins
	
	
	// UART1 to xBee
	U1MODEbits.BRGH = 1;			// Use high speed BAUD rate table
	U1BRG = 17;								// 115,200 bps when FCY = 16MHz
	U1MODEbits.UARTEN = 1;		// Enable UART
	U1STAbits.UTXEN = 1;			// Enables TX hardware
	_U1RXIF = 0;
	_U1RXIP = 3;
	_U1RXIE = 1;


	
	// Timer 2 - PWM Timer
	PR2  						= MAX_DUTY-1;	// Interrupt target
	T2CONbits.TCKPS = 2;				// Prescalar: 3 = 256, 2 = 64, 1 = 8, 0 = 1
	T2CONbits.TON		= 1;				// Turn timer on
	
	
	// Timer 3 - Servo pulses
	PR3  						= 25000;		// Interrupt target
	T3CONbits.TCKPS = 1;				// Prescalar: 3 = 256, 2 = 64, 1 = 8, 0 = 1
	T3CONbits.TON		= 1;				// Turn timer on
	_T3IF = 0;
	_T3IE = 1;
	_T3IP = 7;
	
	// Output Compare Module in PWM Mode
	OC1CONbits.OCM = 6;					// PWM mode on OCx, Fault pin on OCFx disabled
	OC2CONbits.OCM = 6;					// PWM mode on OCx, Fault pin on OCFx disabled
	OC3CONbits.OCM = 6;					// PWM mode on OCx, Fault pin on OCFx disabled
	OC4CONbits.OCM = 6;					// PWM mode on OCx, Fault pin on OCFx disabled
	
	//Interrupt nesting enabled (if 0)
	INTCON1bits.NSTDIS = 0;
	
	SRbits.IPL = 1;	// Set CPU priority so that user interrupts can occour

	while( 1 )
	{
		Idle();
	}
}


/*******************************************************************************
			TIMER 3 INTERRUPT - Servo PWM
*******************************************************************************/
void __attribute__((__interrupt__, no_auto_psv)) _T3Interrupt(void)
{
	static int state = 0;
	static int old_pan = 0;
	static int old_tilt = 0;
	static int pan_timeout = 0;
	static int tilt_timeout = 0;
	
	_T3IF = 0;					// Clears Interrupt Flag

	if( data_received == 0 )
		return;
	
	if( old_pan != pan_servo )
	{
		old_pan = pan_servo;
		pan_timeout = 100;
	}
	if( old_tilt != tilt_servo )
	{
		old_tilt = tilt_servo;
		tilt_timeout = 100;
	}

	if( pan_timeout != 0 )
		pan_timeout--;
	if( tilt_timeout != 0 )
		tilt_timeout--;

	switch( state )
	{
		case 0: if( pan_timeout != 0 )
						{
							PAN_PIN = 1;
						}
						TILT_PIN = 0;
						PR3 = pan_servo;
						state = 1;
				break;

		case 1: PAN_PIN = 0;
						if( tilt_servo != 0 )
						{
							TILT_PIN = 1;
						}
						PR3 = tilt_servo;
						state = 2;
				break;

		case 2: PAN_PIN = 0;
						TILT_PIN = 0;
						PR3 = 25000 - pan_servo - tilt_servo;
						state = 0;
				break;

		default:break;
	}
}



/*******************************************************************************
			UART1 RECEIVE INTERRUPT - Accessory
*******************************************************************************/
void __attribute__((__interrupt__, no_auto_psv)) _U1RXInterrupt(void)
{
	#define IDLE 0
	#define VCTR 1
	#define COLR 2
	
	static int state = IDLE;
	static int received_char_count = 0;
	unsigned int data = 0;
	
	static unsigned char new_pan;
	static unsigned char new_tilt;

	
	_U1RXIF = 0;
	
	data = U1RXREG;
	
	if( received_char_count == 0 )
	{
		if( data == '~' )
		{
			state = VCTR;
			received_char_count = 1;
			data_received = 1;
			return;
		}
		else if( data == '*' )
		{
			state = COLR;
			received_char_count = 1;
			return;
		}
		else
		{
			state = IDLE;

	RED = 100;
			return;
		}
	}
	
	switch( state )
	{
		case VCTR:
		{
			switch( received_char_count)
			{
				case 1:	new_pan = data;
								received_char_count++;
								break;
				case 2: new_tilt = data;
								pan_servo = ( ( ( MAX_PAN_SERVO - MIN_PAN_SERVO ) / 255.0 ) * new_pan ) + MIN_PAN_SERVO;
								tilt_servo = ( ( ( MAX_TILT_SERVO - MIN_TILT_SERVO ) / 255.0 ) * new_tilt ) + MIN_TILT_SERVO;
				default:state = IDLE;
								received_char_count = 0;
			}
			break;
		}
		case COLR:
		{
			switch( received_char_count)
			{
				case 1:	RED = data; // * 0.5;
								received_char_count++;
								break;
				case 2: GREEN = data;
								received_char_count++;
								break;
				case 3: BLUE = data;
								received_char_count++;
								break;
				case 4: WHITE = data;
								received_char_count = 0;
								state = IDLE;
								break;
				default:state = IDLE;
								received_char_count = 0;
			}
			break;
		}
		default:state = IDLE;
						received_char_count = 0;
	}
} // end

Windows GUI

The GUI was developed in Visual Basic 2010 Express (free from Microsoft).

The GUI source files can be downloaded and built within Visual Basic. Currently, the COM port must be set in the source before compiling. If using the PAN1555 breakout board from Digi-Key, pair the device and connect a virtual COM port to determine the COM port number

Imports Microsoft.VisualBasic
Imports System
Imports System.Timers
Imports System.IO.Ports
'Imports System.Threading


Public Module global_variables
    Public timer1_toggle As Boolean = False
    Public timer1_started As Boolean = False
    Public BlueTooth As IO.Ports.SerialPort = My.Computer.Ports.OpenSerialPort("COM33", 115200, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One)
End Module

Public Class Form1

    'Dim FTDI As IO.Ports.SerialPort = My.Computer.Ports.OpenSerialPort("COM1", 115200, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One)

    Dim color_MouseDown As Boolean = False
    Dim vector_MouseDown As Boolean = False

    Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Integer, ByVal x As Integer, ByVal y As Integer) As Integer
    Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Integer) As Integer
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Integer)


    Shared _timer As Timer
    Shared _list As List(Of String) = New List(Of String)

    Private Sub Start()
        _timer = New Timer(5)
        AddHandler _timer.Elapsed, New ElapsedEventHandler(AddressOf Timer1_Handler)
        _timer.Enabled = True
    End Sub

    Private Sub Timer1_Handler(ByVal sender As Object, ByVal e As ElapsedEventArgs)
        Static rgb(0 To 4) As Byte
        Static Dim state As Byte
        Static Dim counter As Byte = 0

        If (timer1_toggle) Then
            rgb(0) = 42
            Select Case state
                Case 0
                    rgb(1) = counter
                Case 1
                    rgb(2) = counter
                Case 2
                    rgb(1) = 255 - counter
                Case 3
                    rgb(3) = counter
                Case 4
                    rgb(2) = 255 - counter
                Case 5
                    rgb(1) = counter
                Case 6
                    rgb(3) = 255 - counter
            End Select

            counter = counter + 1

            If (counter >= 255) Then
                counter = 0
                state = state + 1
                If (state >= 7) Then
                    state = 1
                End If
            End If

            BlueTooth.Write(rgb, 0, 4)

        End If
    End Sub



    Private Sub PictureBox1_MouseDown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.MouseDown
        Dim lColor As Integer
        Dim lDC As Integer
        lDC = GetWindowDC(0)
        lColor = GetPixel(lDC, MousePosition.X, MousePosition.Y)
        Dim red As Byte = lColor Mod 256
        Dim grn As Byte = (lColor \ 256) Mod 256
        Dim blu As Byte = lColor \ 256 \ 256
        Dim rgb(0 To 4) As Byte

        color_MouseDown = True

        UpdateColors(red, grn, blu)

        rgb(0) = 42
        rgb(1) = red
        rgb(2) = grn
        rgb(3) = blu
        BlueTooth.Write(rgb, 0, 4)
    End Sub

    Private Sub PictureBox1_MouseMove(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.MouseMove

        Dim lColor As Integer
        Dim lDC As Integer
        lDC = GetWindowDC(0)
        lColor = GetPixel(lDC, MousePosition.X, MousePosition.Y)
        Dim red As Byte = lColor Mod 256
        Dim grn As Byte = (lColor \ 256) Mod 256
        Dim blu As Byte = lColor \ 256 \ 256
        Dim rgb(0 To 4) As Byte

        If (color_MouseDown) Then
            UpdateColors(red, grn, blu)

            rgb(0) = 42
            rgb(1) = red
            rgb(2) = grn
            rgb(3) = blu
            BlueTooth.Write(rgb, 0, 4)
        End If
    End Sub

    Private Sub PictureBox1_MouseUp(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.MouseUp
        color_MouseDown = False

    End Sub

    Private Sub MovementBox_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MovementBox.MouseDown
        Dim rgb(0 To 3) As Byte
        Dim x As Integer
        Dim y As Integer

        Dim newMousePosition As New System.Drawing.Point

        newMousePosition = Cursor.Position

        ' Send a Tilde (~), then the x and y coordinates of the mouseclick within the picturebox.

        vector_MouseDown = True

        If (e.X < 10) Then
            newMousePosition.X = MovementBox.Left + 10
        ElseIf e.X > 775 Then
            newMousePosition.X = MovementBox.Right - 10
        End If
        If (e.Y < 10) Then
            newMousePosition.Y = MovementBox.Top + 36
        ElseIf e.Y > 775 Then
            newMousePosition.Y = MovementBox.Bottom + 10
        End If

        System.Windows.Forms.Cursor.Position = newMousePosition

        x = MovementBox.Width - e.X - 10
        y = MovementBox.Height - e.Y - 10

        If (y < 0) Then
            y = 0
        ElseIf (y > 765) Then
            y = 765
        End If

        If x < 0 Then
            x = 0
        ElseIf (x > 765) Then
            x = 765
        End If

        TextBox5.Text = x
        TextBox6.Text = y
        rgb(0) = 126
        rgb(1) = x / 3
        rgb(2) = y / 3
        BlueTooth.Write(rgb, 0, 3)

    End Sub

    Private Sub MovementBox_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MovementBox.MouseMove

        Dim rgb(0 To 3) As Byte
        Dim x As Integer
        Dim y As Integer
        Dim newMousePosition As New System.Drawing.Point

        newMousePosition = Cursor.Position

        If vector_MouseDown Then
            If (e.X < 10) Then
                newMousePosition.X = MovementBox.Left + 10
            ElseIf e.X > 775 Then
                newMousePosition.X = MovementBox.Right - 10
            End If
            If (e.Y < 10) Then
                newMousePosition.Y = MovementBox.Top + 36
            ElseIf e.Y > 775 Then
                newMousePosition.Y = MovementBox.Bottom + 10
            End If

            System.Windows.Forms.Cursor.Position = newMousePosition

            x = MovementBox.Width - e.X - 10
            y = MovementBox.Height - e.Y - 10

            If (y < 0) Then
                y = 0
            ElseIf (y > 765) Then
                y = 765
            End If

            If x < 0 Then
                x = 0
            ElseIf (x > 765) Then
                x = 765
            End If

            TextBox5.Text = x
            TextBox6.Text = y
            rgb(0) = 126
            rgb(1) = x / 3
            rgb(2) = y / 3
            BlueTooth.Write(rgb, 0, 3)

        End If
    End Sub

    Private Sub MovementBox_MouseUp(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MovementBox.MouseUp
        vector_MouseDown = False

    End Sub

    Private Sub StrobeButton_MouseDown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StrobeButton.MouseDown
        If (timer1_started = False) Then
            Start()
            timer1_started = True
        End If
        If (timer1_toggle = True) Then
            timer1_toggle = False
        Else
            timer1_toggle = True
        End If
    End Sub

    Public Sub UpdateColors(ByVal red As Byte, ByVal grn As Byte, ByVal blu As Byte)
        Button1.Text = red
        Button2.Text = grn
        Button3.Text = blu
        BackColor = Color.FromArgb(red, grn, blu)
    End Sub

    Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click
        Dim lColor As Integer
        Dim lDC As Integer
        lDC = GetWindowDC(0)
        lColor = GetPixel(lDC, MousePosition.X, MousePosition.Y)
        Dim red As Byte = lColor Mod 256
        Dim grn As Byte = (lColor \ 256) Mod 256
        Dim blu As Byte = lColor \ 256 \ 256
        Dim rgb(0 To 4) As Byte

        color_MouseDown = True

        UpdateColors(red, grn, blu)

        rgb(0) = 42
        rgb(1) = red
        rgb(2) = grn
        rgb(3) = blu
        BlueTooth.Write(rgb, 0, 4)
    End Sub

    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)

    End Sub
End Class
  • No labels

2 Comments

  1. Hello

     

    Project looks great and am looking forward to getting it to work. Was curious on the GUI, downloaded Visual Basic 2010 but program will not compile? Was wondering if anyone could give me some hints. Thanks you.

    Cheers, Michael

    1. "Was curious on the GUI, downloaded Visual Basic 2010 but program will not compile?"

      Can you be more specific? Error messages, etc.