A color changing desk lamp using ws2801 strip LEDs and a ch340g USB serial adapter

ws2801_strip

I’ve been using Redshift on my desktop for awhile now and have wanted my keyboard light to change color at night as well.  I’d been using a small string of x-mas lights because they offered a nicely spread out light that wasn’t excessively bright at night but they weren’t adjustable in color or brightness.

This project requires soldering, including soldering two small wires to the pins of an SOIC-16 chip.  Even with cheap, poor quality tools, it’s possible, though I did use a solder puller to clean up my first attempt.

Ebay and China sellers have shorter 5 volt ws2801 led strips for under $5 and the USB to serial adapters are only about 60 cents.   My preference is the ch340g serial adapters over FTDI, they have no history of abusing customers who unknowingly bought clones, and the drivers are built in to Linux.

The ws2801 was chosen over the ws2811B due to the tight timing restrictions of the ws2811B, controlling those is not very practical without a micro-controller which would double the price of this project, so make sure you get the right one.

The Hardware:

  • If your strip has wires attached, identify the input end.  Then identify the colors for each pin.  Mine were Blue = Ground, Red = Data, Green = Clock, and Black = +5V.  You can use a meter or continuity test to find Ground and +5V, but you’ll have to look under the shrink tubing to identify Data and Clock.
  • Solder wires to pins 13 and 14 of the ch340g chip on the USB serial adapter.  13 will be Data and 14 will be Clock.  If you’re using the stranded wires that came with your LED strip, you may have to remove a few strands to get the wire to fit on the pin.  I was down to about 5 strands for mine.  Twisting and tinning these wires first helps.
  • Solder wires to the Ground and +5 terminals, also soldering the voltage select terminal to the 5V terminal on the USB serial adapter.CH340G_Adapter
  • Solder all 4 wires to the LED strip (if they weren’t already attached)
  • Cut your strip to length.  Each LED set uses up to 60 milliamps and USB only provides 500 milliamps, so that works out to 8 1/3 RGB LEDs.  I went with 9 for my strip and use nothing else on that port, even though that’s pushing it a little bit.

The software:

Save this code as ws2801write.c and compile with:

 gcc -o ws2801write ws2801write.c

ws2801write.c

/* 32 LEDs is 9.6W or 1.92A
500mA USB is 8.33 LEDs

The pixels are connected by a 4-conductor cable.
+5V (Red)
Ground (Blue)
Data (Yellow) -- DTR
Clock (Green) -- RTS

Clock starts low for at least 500uS.

Data is clocked out 24 bits at a time, in Red, Green, Blue order with msb sent first.

Each bit is captured on rising edge of clock.

Data is latched by holding clock pin low for 1 millisecond.

Syntax:
  ws2801write [ -p <device> ] [ -l <string length> ] [ -c D[TR] | R[TS] ] [ -d D[TR] | R[TS] ] <red> <green> <blue> [ <red> <green> <blue> ]
    -p serial device file, defaults to /dev/ttyUSB0
    -l number of pixels in the string, defaults to 64
    -c clock pin, defaults to RTS
    -d data pin, defaults to DTR
    <red> <green> <blue> sequences are decimal 0-255 each and when fewer sequences are provided than
      <string length>, the final sequence is repeated until <string length> after the sequences run out

reference:
LED string: https://www.aliexpress.com/item//32709231420.html
USB->serial: https://www.aliexpress.com/item//32668866076.html
Datasheet: Page 12 of http://longhornengineer.com/github/Appnotes_WS2801_MOSFET/WS2801_Datasheet.pdf .
Additional information:
   end clock: https://github.com/adafruit/Adafruit-WS2801-Library/blob/master/Adafruit_WS2801.cpp
   DTR/RTS in linux: http://xanthium.in/Controlling-RTS-and-DTR-pins-SerialPort-in-Linux
*/

    #include <stdio.h>
    #include <fcntl.h>       /* File Control Definitions           */
    #include <termios.h>     /* POSIX Terminal Control Definitions */
    #include <unistd.h>      /* UNIX Standard Definitions          */
    #include <errno.h>       /* ERROR Number Definitions           */
    #include <sys/ioctl.h>   /* ioctl()                            */
    #include <stdlib.h>
    #include <stdint.h>
    #include <time.h>

    void sendByte(int fd, uint8_t byte, int dataPin, int clockPin);
    void sleepuS(unsigned long uS);
    int main(int argc, char *argv[]) {
        int fd=-1;
        char *deviceName = "/dev/ttyUSB0";
        unsigned int sLength = 64;
        int clockPin = TIOCM_RTS;
        int dataPin = TIOCM_DTR;
        unsigned int curPixel = 0;
        unsigned int arg;
        int red=255;
        int green=255;
        int blue=255;

        for( arg = 1; arg < argc; arg++ ) {
            switch( argv[arg][0] ) {
                case '-' : // got a command line switch, identify and do it
                    switch( argv[arg][1] ) {
                        case 'p' :
                            deviceName=argv[++arg];
                            break;
                        case 'l' :
                            sLength = atoi(argv[++arg]);
                            break;
                        case 'c' :
                            if( argv[++arg][0]=='R' ) {
                                clockPin = TIOCM_RTS;
                                dataPin = TIOCM_DTR;
                            } else if( argv[arg][0]=='D' )  {
                                clockPin = TIOCM_DTR;
                                dataPin = TIOCM_RTS;
                            } else {
                                printf( "Invalid option: -c %s\n", argv[arg] );
                            }
                            break;
                        case 'd' :
                            if( argv[++arg][0]=='D' ) {
                                clockPin = TIOCM_RTS;
                                dataPin = TIOCM_DTR;
                            } else if( argv[arg][0]=='R' )  {
                                clockPin = TIOCM_DTR;
                                dataPin = TIOCM_RTS;
                            } else {
                                printf( "Invalid option: -d %s\n", argv[arg] );
                            }
                            break;
                        default :
                            printf( "Invalid option: %s\n", argv[arg] );
                            exit(-1);
                    }
                    break;
                case '0' :
                case '1' :
                case '2' :
                case '3' :
                case '4' :
                case '5' :
                case '6' :
                case '7' :
                case '8' :
                case '9' : // got a number, send the colors
                    if( fd == -1 ) {
                        fd = open( deviceName, O_RDWR | O_NOCTTY );
                        if(fd == -1) {                                   /* Error Checking */
                            printf("Error Opening %s\n", deviceName );
                            exit(-2);
                        }
                        ioctl(fd,TIOCMBIS,&clockPin); //set clock low to init
                        sleepuS(600);
                    }
                    red=green=blue=0;
                    if(arg < argc) red=atoi(argv[arg++]);
                    if(arg < argc) green=atoi(argv[arg++]);
                    if(arg < argc) blue=atoi(argv[arg]);
                    sendByte(fd, red, dataPin, clockPin);
                    sendByte(fd, blue, dataPin, clockPin); //note green and blue reversed on my strip
                    sendByte(fd, green, dataPin, clockPin); //swap these lines if yours are not reversed.
                    sLength--;
                    if( sLength <= 0 ){
                       ioctl(fd,TIOCMBIS,&clockPin); //set clock low to latch
                       sleepuS(600); //wait for latch before closing
                       close(fd);
                       exit(0);
                    }
                    break;
                 default :
                   printf( "Invalid option: %s\n", argv[arg] );
                   exit(-1);
            }
        }
        for( ; sLength > 0; sLength-- ) { //fill the remaining LEDs with the last color given
           sendByte(fd, red, dataPin, clockPin);
           sendByte(fd, blue, dataPin, clockPin); //note green and blue reversed on my strip
           sendByte(fd, green, dataPin, clockPin); //swap these lines if yours are not reversed.
        }
        ioctl(fd,TIOCMBIS,&clockPin); //set clock low to latch
        sleepuS(600); //wait for latch before closing
        close(fd);
    }

    void sendByte(int fd, uint8_t byte, int dataPin, int clockPin) {
    int b;
        for( b=0; b<8; b++){
            if( !!(byte & (1 << ( 7 - b ))) ){
                ioctl(fd,TIOCMBIC,&dataPin);//clear is on
//                printf("1");
            } else {
                ioctl(fd,TIOCMBIS,&dataPin);//set is off
//                printf("0");
            }
            ioctl(fd,TIOCMBIC,&clockPin);//data clocked on rising edge (clear is on)
            ioctl(fd,TIOCMBIS,&clockPin);//return to off
        }
//        printf("\n");
    }

    void sleepuS(unsigned long uS){
        struct timespec ts;
        ts.tv_sec = 0;
        ts.tv_nsec = uS*1000L; //1 uS (1 millionths/1000 billionths)
        nanosleep(&ts, NULL);
    }

The syntax is:

ws2801write [ -p  ] [ -l  ] [ -c D[TR] | R[TS] ] [ -d D[TR] | R[TS] ]    [    ]
    -p serial device file, defaults to /dev/ttyUSB0
    -l number of pixels in the string, defaults to 64
    -c clock pin, defaults to RTS
    -d data pin, defaults to DTR
       sequences are decimal 0-255 each and when fewer sequences are provided than
      , the final sequence is repeated until  after the sequences run out

Examples:   9 full on red LEDs:
./ws2801write -l 9 255

9 bright white (a bit blueish on mine)
./ws2801write -l 9 255 255 255

3 bright blue, 3 bright green, and 3 bright red
./ws2801write 0 0 255 0 0 255 0 0 255 0 255 0 0 255 0 0 255 0 255 0 0 255 0 0 255 0 0

9 dim redish
./ws2801write -l 9 127 80 35

9 medium redish
./ws2801write -l 9 255 200 100

Note, some machines may interrupt the transfer process and cause odd colors.  nice -20 ./ws2801write -l 9 255 255 255 may resolve this OS problem.

Advertisements

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