A standard $35 Raspberry Pi computer runs a program that generates the onscreen interface, and sends commands via USB to a DMX controller continuously. The DMX controller then sends DMX commands to the light fixture to change the color. The hardware was chosen for cost and durability, and the software was developed on the platform and is available for free in source and binary format so you can begin hacking right away. Here is what you'll need ( as pictured ):
1. Raspberry Pi
The Raspberry Pi is an amazing $35 credit card sized computer that contains all the basic features of a "real" computer, including a free pre-built Linux operating system ( via SD card ), USB for keyboard and mouse, and most impressively a full HDMI video output. If you are new to the Raspberry Pi ( or raspi ) we strongly suggest you get your mitts on one as soon as possible - its a great platform for anyone interested in knowing more about how computers work, and you can actually write programs on it!
2. USB | DMX Controller
The raspi has a built-in I/O connector for doing all sorts of cool things, but for this project we chose to use one of the USB ports as our output interface because its simpler and more rugged than using breadboards and ribbon cables. To get things into DMX (digital light control) format, we will be using a Velleman USB to DMX interface. This can be bought in either kit or pre-built form, and is a really a great introduction to controlling DMX devices from a computer. Once you have this controller you'll find it a great tool for any lighting control project you may cook up in the future.
3. DMX light fixture
Pretty much any DMX controlled light fixture that has red, green, or blue channels will work, and in fact you can chain together several if you want t control a whole bank of lights from your raspi. In this example we are using a Chauvet LEDSplash 200B spotlight because we found one cheap online for about $60 and its very bright and runs cool. If you have a DMX dimmer and standard PAR cans that's fine too, its only important that you have a device that can receive red, green, and blue intensity channels.
4. HDMI ( or NTSC monitor )
Perhaps the best feature of the raspi is its HDM interface ( compare with Arduino video output ), which provides a full 1920x1080 graphics resolution to any TV screen that has an HDMI input. In this example we used a cheap Vizio monitor that we had in our kitchen, and functions nicely for a video monitor. It might be interesting to to use this kind of system as a starting point for a TV back light project or similar living room light effects when you move it into your living room since you have the video interface right there.
http://www.engeldinger.com/services/latest-project/dmxwheel
Let me know if you run into any problems getting it working.
// ==========================================================================
// Velleman K8062 DMX controller library for VM116/K8062
// ==========================================================================
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include "dmx.h"
int * maxChanAddr; // control register for # of channels to update
ubyte * exitAddr; // control register to exit deamon
ubyte * chanData; // 512 byte array of channel data
ubyte * shm; // shared memory segment containing data & ctrl regs
int shmid = -1; // handel to shared memory segment
// ==========================================================================
// open the DMX connection
// ==========================================================================
int dmxOpen()
{
// get the shared memory created by the deamon
shmid = shmget ( 0x56444D58 , sizeof ( ubyte ) * 515 , 0666 );
if ( shmid == -1 ) {
printf ( "error[%d] - is dmxd running?\n" , errno );
return ( errno );
}
// set up control and data registers
shm = ( ubyte *) shmat ( shmid, NULL, 0 );
maxChanAddr = ( int * ) shm;
exitAddr = ( ubyte * ) maxChanAddr + 2;
chanData = ( ubyte * ) maxChanAddr + 3;
}
// ==========================================================================
// close the DMX connection
// ==========================================================================
void dmxClose()
{
if ( shmid != -1 ) shmdt ( shm );
}
// ==========================================================================
// dmxSetMaxChannels -- set the maximum # of channels to send
// ==========================================================================
void dmxSetMaxChannels ( int maxChannels )
{
*maxChanAddr = maxChannels;
}
// ==========================================================================
// dmxSetValue -- set the value for a DMX channel
// ==========================================================================
void dmxSetValue ( ubyte channel , ubyte data )
{
chanData[channel] = data;
}
// ==========================================================================
// Velleman K8062 DMX controller library for VM116/K8062
// ==========================================================================
typedef unsigned char ubyte;
int dmxOpen ();
void dmxClose ();
void dmxSetMaxChannels ( int maxChannels );
void dmxSetValue ( ubyte channel , ubyte value );
// ==========================================================================
// Velleman K8062 DMX controller Deamon for VM116/K8062
// ==========================================================================
//
// Modified from code from Denis Moreaux 2008 ( <vapula@endor.be> )
//
//
// This program should be run as a background process and continously updates
// a shared memory segment created by the application to control DMX channels
// sent through the DMX controller. The DMX channels can be accessed through
// a shared memory block that is allocated as:
//
// 0 = max # of channels to send ( 0 - 512 )
// 1 = exit deamon control flag ( 0 = run, 1 = exit )
// 2-514 = dmx channel data
//
// ==========================================================================
//
// Prerequisites ( USB lib ):
// sudo apt-get install libusb-dev
//
//
// ==========================================================================
//
#include <usb.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>
// dmx data and control registers
typedef unsigned char ubyte;
int * maxChanAddr; // control register for # of channels to update
ubyte * exitAddr; // control register to exit deamon
ubyte * chanData; // 512 byte array of channel data
ubyte *shm; // shared memory segment containing data & ctrl regs
int shmid; // handel to shared memory segment
// constants and defs
#define ProgName "dmxd" // name of this program
#define VendorID 0x10cf // K8062 USB vendor ID
#define ProdID 0x8062 // K8062 USB product ID
#define UpdateInt 100000 // update interval ( microseconds )
#define DefMaxChans 16 // default number of maximum channels
// internal structures
struct usb_bus *bus; // pointer to the USB bus
struct usb_device *dev; // pointer to the K8062 USB device
usb_dev_handle *udev; // access handle to the K8062 device
// function delcarations
int main();
int sendDMX();
int initUSB();
int writeUSB ( ubyte *data , int numBytes );
void exitUSB();
int initSHM();
void exitSHM();
void timediff ( struct timeval *res, struct timeval *a, struct timeval *b );
void timeadd ( struct timeval *res, struct timeval *a, struct timeval *b );
// ==========================================================================
// main -- dmx deamon
// ==========================================================================
int main() {
struct timeval now,next,diff,delay;
int success;
printf ( "%s: starting dmx deamon\n" , ProgName );
// intialize USB device
success = initUSB();
if ( !success ) {
printf ( "%s: error initializing USB interface\n" , ProgName );
return ( -1 );
}
// initialize shared memory segment
success = initSHM();
if ( !success ) {
printf ( "%s: error initializing shared memory\n" , ProgName );
return ( -2 );
}
// start timer
delay.tv_sec = 0;
delay.tv_usec= UpdateInt;
gettimeofday ( &next , NULL );
// loop until commanded to shutdown
while( !*exitAddr ) {
// send DMX data
success = sendDMX();
if ( !success ) {
printf ( "%s: DMX send error\n" , ProgName );
*exitAddr++;
}
// wait for update interval
timeadd ( &next , &next , &delay );
gettimeofday ( &now , NULL );
timediff ( &diff, &next , &now );
while (diff.tv_sec || diff.tv_usec) {
select ( 0, NULL, NULL, NULL, &diff );
gettimeofday ( &now, NULL );
timediff ( &diff, &next, &now );
};
}
printf ( "%s: dmx deamon is shutting down\n" , ProgName );
// on shutdown reset all DMX channels
memset ( chanData , 0, 512 * sizeof (ubyte) );
sendDMX();
// exit the system
exitUSB();
exitSHM();
return ( 0 );
}
// ==========================================================================
// sendDMX -- send current DMX data
// ==========================================================================
int sendDMX ()
{
ubyte data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
int numChans = *maxChanAddr;
#if 1
// find out how many consecutive zeroes are in the data - the start
// packet can indicate this to avoid sending a bunch of leading
// zeroes
int curChanIdx = 0;
for ( curChanIdx = 0; curChanIdx < numChans; curChanIdx++ ) {
if ( chanData[ curChanIdx ] != 0 ) break;
}
// build starting packet. this packet specifies how many channels have
// zero data from the start and then contains the next 6 channels of
// data
data[0] = 4; // start packet header (4)
data[1] = curChanIdx; // number of zeroes ( not sent )
data[2] = chanData [ curChanIdx++ ]; // first ( non-zero ) chan data
data[3] = chanData [ curChanIdx++ ]; // next chan data
data[4] = chanData [ curChanIdx++ ]; // next chan data
data[5] = chanData [ curChanIdx++ ]; // next chan data
data[6] = chanData [ curChanIdx++ ]; // next chan data
data[7] = chanData [ curChanIdx++ ]; // next chan data
int success = writeUSB ( data , 8 );
if ( !success ) {
printf ( "%s: error sending DMX start packet\n" , ProgName );
return ( 0 );
}
if ( curChanIdx >= numChans ) return ( 1 );
// after the first packet additional packets are sent that contain seven
// channels each up to 512.
while ( curChanIdx < ( numChans - 7 ) ) {
data[0] = 2; // start packet header (2)
data[1] = chanData [ curChanIdx++ ]; // next chan data
data[2] = chanData [ curChanIdx++ ]; // next chan data
data[3] = chanData [ curChanIdx++ ]; // next chan data
data[4] = chanData [ curChanIdx++ ]; // next chan data
data[5] = chanData [ curChanIdx++ ]; // next chan data
data[6] = chanData [ curChanIdx++ ]; // next chan data
}
success = writeUSB ( data , 8 );
if ( !success ) {
printf ( "%s: error sending DMX bulk packet\n" , ProgName );
return ( 0 );
}
if ( curChanIdx >= numChans ) return ( 0 );
#else
data[0] = 5; // packet header for single channeld data
printf ( "sending %d channels\n" , numChans );
for ( int chIdx = 0; chIdx < numChans; chIdx++ )
{
data[1] = chanData [ chIdx ];
int success = writeUSB ( data , 8 );
if ( !success ) {
printf ( "%s: error sending DMX data packet\n" , ProgName );
return ( 0 );
}
}
#endif
return ( 1 );
}
// ==========================================================================
// initUSB -- intialize the USB interface for the device
// ==========================================================================
int initUSB()
{
int success;
// open the usb library
usb_init();
// find the usb device for DMX controller
usb_find_busses();
usb_find_devices();
usb_device_descriptor *descr = 0x0;
for ( bus = usb_busses; bus; bus = bus -> next ) {
for ( dev = bus->devices; dev; dev = dev -> next ) {
printf ( "%s: checking device [%s]\n" , ProgName , dev -> filename );
descr = & dev->descriptor;
if ( ( descr -> idVendor == VendorID )
&& ( descr -> idProduct == ProdID ) ) break;
}
}
if ( !dev ) {
printf ( "%s: DMX device not found on USB\n" , ProgName );
return ( 0 );
}
// open the device
printf ( "%s: opening device [%s] ... " , ProgName , dev -> filename );
udev = usb_open ( dev );
if ( udev == 0x0 ) {
printf ( "%s: error opening device\n" , ProgName );
return ( 0 );
}
else {
printf ( "ok\n" );
}
// claim the interface
#if defined(LIBUSB_HAS_GET_DRIVER_NP) \
&& defined(LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP)
usb_detach_kernel_driver_np( udev, 0);
#endif
// set configuration
usb_set_debug(4);
success = usb_set_configuration ( udev, 1 );
if ( success != 0 ) {
printf ( "%s: configuration error [%d]\n" , ProgName , success );
return ( 0 );
}
// claim the interface
success = usb_claim_interface ( udev, 0 );
if ( success != 0 ) {
printf ( "%s: error claiming interface [%d]\n" , success );
return ( 0 );
}
return ( 1 );
}
// ==========================================================================
// writeUSB -- write a command to the USB interface
// ==========================================================================
int writeUSB ( ubyte *data , int numBytes )
{
int nSent;
// printf ( "%s: writing [%d] bytes " , ProgName , numBytes );
// for ( int b = 0; b < numBytes; b++ ) printf ( "[%d]" , data[b] );
// printf ( "\n" );
// write the data
nSent = usb_interrupt_write ( udev ,
1,
(char *) data,
numBytes,
200 );
if ( nSent != numBytes ) {
printf ( "%s: error writing [%d] bytes [%d]\n" , numBytes , nSent );
return ( 0 );
}
return ( 1 );
}
// ==========================================================================
// exitUSB -- terminate USB connection
// ==========================================================================
void exitUSB()
{
usb_close(udev);
}
// ==========================================================================
// initSHM -- initialize shared memory segment
// ==========================================================================
int initSHM()
{
printf ( "%s: creating shared memory segment ... " , ProgName );
// create the shared memory segment
shmid = shmget ( 0x56444D58 , sizeof ( ubyte ) * 515 , IPC_CREAT | 0666 );
if ( shmid == -1 ) {
printf ( "error creating shared memory segment [%d]\n" , errno );
return ( 0 );
}
else
printf ( "ok\n" );
// attach to segment and initialize
printf ( "%s: intitalizing segment [0x%x] ... " , ProgName , shmid );
shm = ( ubyte * ) shmat ( shmid , NULL , 0 );
if ( shm == 0x0 ) {
printf ( "error connecting to segment [%d]\n" , errno );
return ( 0 );
}
else
printf ( "ok\n" );
memset ( shm , 0 , sizeof ( ubyte ) * 515 );
// set up command & data registers
maxChanAddr = ( int * ) shm;
*maxChanAddr = DefMaxChans;
exitAddr = ( ubyte * ) maxChanAddr + 2;
chanData = ( ubyte * ) maxChanAddr + 3;
return ( 1 );
}
// ==========================================================================
// exitSHM -- terminate shared memory segment
// ==========================================================================
void exitSHM()
{
shmdt(shm);
shmctl(shmid,IPC_RMID,NULL);
}
// ==========================================================================
// timediff | timeadd -- timing functions
// ==========================================================================
void timediff ( struct timeval *res, struct timeval *a, struct timeval *b)
{
long sec,usec;
sec=a->tv_sec-b->tv_sec;
usec=a->tv_usec-b->tv_usec;
while (usec<0) {
usec+=1000000;
sec--;
}
if (sec<0) {
res->tv_sec=0;
res->tv_usec=0;
} else {
res->tv_sec=sec;
res->tv_usec=usec;
}
}
void timeadd(struct timeval *res, struct timeval *a, struct timeval *b)
{
res->tv_usec=a->tv_usec+b->tv_usec;
res->tv_sec=a->tv_sec+b->tv_sec;
while (res->tv_usec >= 1000000) {
res->tv_usec-=1000000;
res->tv_sec++;
}
}
CC=g++
DEAMONOBJS=dmxd.o
LIBOJBS=dmx.o
OBJS=$(LIBOBJS) $(DEAMONOBJS)
DEAMONBIN=dmxd.bin
LIB=libdmx.a
CFLAGS+=
LDFLAGS+=-lusb -lm
INCLUDES+=-I./
all: $(LIB) $(DEAMONBIN) $(TESTBIN)
%.o: %.c
@rm -f $@
$(CC) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations
dmxd.bin: $(DEAMONOBJS)
$(CC) -o $@ -Wl,--whole-archive $(DEAMONOBJS) $(LDFLAGS) -Wl,--no-whole-archive -rdynamic
mv dmxd.bin ../deamon
%.a: $(LIBOJBS)
$(AR) r $@ $^
mv $(LIB) ../lib
cp dmx.h ../include
clean:
for i in $(OBJS); do (if test -e "$$i"; then ( rm $$i ); fi ); done
@rm -f dmxd.bin $(LIB)
What would make it even more useful would be able to use a smartphone to be able to control it. My preference is for Android but I believe there is another popular mobile OS out there too.
Certainly would be interesting, but the interface side would take a great deal of work and thinking to be useful (perhaps something using a touchscreen could be done, not the cheapest but it would be less complicated in hardware terms). I had talked about a much simpler converter, but of course that does not need all the power of a Pi.
Sorry, I could not help with this project (don't own a Pi), but it really is surprising that this has not been done before.
Thanks for your comment.