Hello all. My friends and I are the college stoner types, so LEDs and awesome ways to use them have been a #1 incentive for learning to Arduino. I've been tinkering for a while now and basically have a couple of feasibility questions for those with more wisdom than I ![]()
Currently our prototype uses five common-cathode RGB LEDs (ebay) along with current-limiting resistors (100Z) and two 595 shift registers to produce 15-bit (5 per channel) color on the arduino. Attached is some code, and you will see how enamored I was with the bit-angle-modulation principle. I'll try recording some video when I get a chance, but trust me when I say I'm thoroughly impressed with $0.25 chinese baubles.
#include <TimerOne.h>
#include "SPI.h"
#include "Binary.h"
#include "Math.h"
/* Two shift registers (16 bits) are used with five RGB LEDs
** for a total of 15 bits used for color information.
** Physically the bits represent:
** XBGRBGRB GRBGRBGR <- bitstream
** so I will use LSB-first so the bytes are utilized:
** RGBRGBRG BRGBRGBX for (uint) spiBuffer[timeslice]
** 15-bit color stores information like this?
** RRRRRGGGGGBBBBB so 32 levels of red or green or blue
** total 32768 color choices, much better than just 256.
** so the art buffer looks like this:
** uint16 pixelBuffer[5] = {XRRRRRGGGGGBBBBB, ...
** and the precalculated buffer looks like:
** timeslice = 0...4 (5-bit color for each channel)
** First pixel RGBRGBRGBRGBRGBX for sixteen bits output.
** SPI send happens LSB-first, so send spiBuf & 0xFF
** and then send spiBuf[timeslice] >> 8 & 0xFF
** to fill both registers, unused output first.
*/
byte dataPin = 11;Â // MOSI
byte clockPin = 13;Â // SCKÂ
byte latchPin = 10;Â // CS/LATCH. set this pin LOW before sending data, and HIGH when finished
int t = 16;Â Â Â Â // milliseconds between common delays
byte refresh = 500;Â // microseconds between display updates, 500us = 2000Hz
unsigned int pixelBuffer[5]; // fifteen bit color, XRRRRRGGGGGBBBBB
unsigned int spiBuffer[5]; // so five distinct time slices now.
byte whichSlice = 0; // now goes from 0...30 (31 levels plus 0 gives 32 possible brightness per color channel)
boolean busyUpdating = false;
// how to use the 5-bit:Â Â Â XRRRRRGGÂ Â Â Â Â GGGBBBBB
unsigned int BLACK =Â ((int)B00000000 << 8) | B00000000;
unsigned int RED =Â Â ((int)B01111100 << 8) | B00000000;
unsigned int YELLOW =Â ((int)B01111111 << 8) | B11100000;
unsigned int GREEN =Â ((int)B00000011 << 8) | B11100000;
unsigned int CYAN =Â Â ((int)B00011111 << 8) | B11111111;
unsigned int BLUE =Â Â ((int)B00000000 << 8) | B00011111;
unsigned int MAGENTA = ((int)B01111100 << 8) | B00011111;
unsigned int WHITE =Â ((int)B01111111 << 8) | B11111111;
unsigned int colorPalette[8] = { BLACK, RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA, WHITE };
// <-- from http://blog.appliedplatonics.com/2009/05/14/hack-of-the-day-led-hsv-wheel/
int hsv_to_rgb(float myHue, float mySat, float myVal) {
Â
 byte max_val = 31;
 int h_i = ((int)(myHue/60)) % 6;
 float f = (myHue/60) - (int)(myHue/60);
 float myRed,myGreen,myBlue;
Â
 float p = myVal * (1.0 - mySat);
 float q = myVal * (1.0 - f*mySat);
 float t = (1.0 - (1.0 - f)*mySat);
Â
 switch(h_i) {
  case 0: myRed = myVal; myGreen = t; myBlue = p; break;
  case 1: myRed = q; myGreen = myVal; myBlue = p; break;
  case 2: myRed = p; myGreen = myVal; myBlue = t; break;
  case 3: myRed = p; myGreen = q; myBlue = myVal; break;
  case 4: myRed = t; myGreen = p; myBlue = myVal; break;
  case 5: myRed = myVal; myGreen = p; myBlue = q; break;
 }
Â
 byte newRed = max_val - (char)(max_val*myRed);
 byte newGreen = max_val - (char)(max_val*myGreen);
 byte newBlue = max_val - (char)(max_val*myBlue);
Â
 return(generateColor(newRed, newGreen, newBlue));
}
void setup() {
 DDRB |= B00111111; // pins 8 through 13 are now output
 //Serial.begin(9600);
 SPI.begin();
 SPI.setClockDivider(SPI_CLOCK_DIV2);
 SPI.setBitOrder(LSBFIRST);
 Timer1.initialize(refresh);
 Timer1.attachInterrupt(updateEverything);
}
void updateEverything() {
 if (!busyUpdating) {
  busyUpdating = true;
  switch (whichSlice) {
   case 15:
    sendSPI(0);
    break;
   case 14:
   case 16:
    sendSPI(1);
    break;
   case 12:
   case 13:
   case 17:
   case 18:
    sendSPI(2);
    break;
   case 8:
   case 9:
   case 10:
   case 11:
   case 19:
   case 20:
   case 21:
   case 22:
    sendSPI(3);
    break;
   default:
    sendSPI(4);
    break;
  }
  whichSlice = ++whichSlice %31;
  busyUpdating = false;
 }
}
void sendSPI(byte mySlice) {
 digitalWrite(latchPin, LOW);
 SPI.transfer(spiBuffer[mySlice] & 0xFF);
 SPI.transfer((spiBuffer[mySlice] >> 8) & 0xFF);
 digitalWrite(latchPin, HIGH);
}
void precalc() { // fill up spiBuffer with proper data
 for (byte mySlice = 0; mySlice <=4; mySlice++) {
  spiBuffer[mySlice] = 0;
  for (byte myPixel = 0; myPixel <=5; myPixel++) {
   spiBuffer[mySlice] |= (((pixelBuffer[myPixel] >> mySlice+0) & 1) << (myPixel*3)); // BLUE
   spiBuffer[mySlice] |= (((pixelBuffer[myPixel] >> mySlice+5) & 1) << ((myPixel*3)+1)); // GREEN
   spiBuffer[mySlice] |= (((pixelBuffer[myPixel] >> mySlice+10) & 1) << ((myPixel*3)+2)); // RED
  }
  spiBuffer[mySlice] = spiBuffer[mySlice] << 1; // unused pixel is the LSB
 }
}Â
void setPixel(byte myPixel, int myColor) {
 pixelBuffer[myPixel] = myColor;
}
int generateColor(byte myRed, byte myGreen, byte myBlue) {
 int myColor = 0;
 myColor |= ((myRed & 31) << 10); // we use & 31 to ensure the value does not exceed 31
 myColor |= ((myGreen & 31) << 5); // the << shift puts Red data on the left, with Green and Blue following it
 myColor |= ((myBlue & 31) << 0);
 return myColor;
}
void colorFades() {
 float h = 0.0;
 float s = 1.0;
 float v = 1.0;
Â
 while (h<360) {
  h++;
  for (byte i = 0; i <=4; i++) {
   pixelBuffer[i] = hsv_to_rgb((h+(25*i)%360),s,v);
  }
  precalc();
  delay(t);
 }
}
void loop() {
 colorFades();
}
My first question is simply one of coding principles-- I wouldn't mind a couple critiques for cleaning up my code, though it is plenty fast enough for me already due to the 8MHz SPI! Actually I wouldn't be surprised if it's totally unreadable as I was in some sort of mathematical trance when I figured out the bitshifting!
The second is artistry and color-- I know there are people with more artistic flair than I, so I'm looking for hints as to a way to tie my affinity for maths into a pleasing light display. Obviously the basic schematic is extendible in units of 2x 595 = 5 pixels, though the precalculation of register data would get more complicated.
Finally I'm asking a basic electronics question. Now that I've got my coding chops on handling the 595s, I'd like to try my hand at a more permanent installation, like a wall-mounted lamp. I ordered some high-brightness 5mm LEDs (Vf = 1.8-2.3 for red, 3.2-3.4 for green, I = 20mA) and I want to string them in series so my wall-lamp is powered by 12V wall-wart. A 2N3904 is more than adequate for a string of 4 reds or 3 greens, right? Would I need a resistor between the 595 output and the base of the transistor?