595's and LEDs hooray

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 :slight_smile:

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?

Would I need a resistor between the 595 output and the base of the transistor?

Yes.

Code looks fine to me.

so I'm looking for hints as to a way to tie my affinity for maths into a pleasing light display.

Think of colour space (google it). This can be cubic (basic) or hexcone you can wander about from colour to colour like you wander around in space. Depending on the coordinate system you choose cubic or hexcone you get diffrent results going from one colour to the next.

Thanks for the reply Grumpy.

For the 2N3904, if I am switching a load of ~20mA would a 4.7k base resistor be appropriate? I'm still not clear on "saturated" operation (linear for amplifiers, saturated for switches?)

What if the load were closer to the 200mA maximum collector current? (several 20mA LED strings in parallel)

Also, when calculating the current limiting resistor for the LED strings, do I assume the transistor drop is 0.7V?

if I am switching a load of ~20mA would a 4.7k base resistor be appropriate?

Yes, as the base current would be 5 - 0.7 = 4.3V through 4K7 -> 4.3 / 4.7 = 0.9mA
This requires your transistor to have a gain of at least 22 - this is fine for most transistors.

What if the load were closer to the 200mA maximum collector current?

So the minimum gain you would need from your transistor would be 220
This is closer to a typical gain so you might be better giving the transistor more base current.

I'm still not clear on "saturated" operation

When a transistor is saturate it can not conduct any more, so no matter how much more base current you supply you will get no more collector current. We sometimes say this is hard on. The voltage from the collector to the emitter in this state is called the saturation voltage and varies according to what transistor you have, see the data sheet for your transistor. These are typically from 0.7V to 2V.

do I assume the transistor drop is 0.7V?

No use the saturation voltage of the transistor.

According to http://www.taydaelectronics.com/datasheets/A-111.pdf

Vbe(sat) is 0.65-0.95V at Ic = 50mA, and this is the voltage I use for calculating the appropriate base resistor, right? the 0.7V figure you used above that is.

But Vce(sat) is 0.2-0.3V at Ic = 50mA, which is definitely nowhere near your range of 0.7-2V.

Do we want saturated operation?

Supposing a 9V supply and LEDs with a Vf = 2V @ 20mA, I would construct the following circuit:

                  Collector -R2- LED(x4) --- +9V
595 output -R1- Base [2N3904]
                   Emitter --- Gnd

If I want 20mA through the LEDs, then Ic needs to be 20mA, where Ic = current through the LED's resistor. Since Vf = 2V, and the saturation voltage of the transistor is 0.3V, then the voltage drop across the resistor must be 0.7V, which means R2 should be (0.7/0.02) = 35 Ohm?
Then the base current should be fine at 0.9mA with the Vbe = 0.7V drop combined with R1 = 4k7.

But if I wanted to wire 6 such LED string in parallel (for a total LED current of 120mA) I will need additional base current, so perhaps R1 = 1k (Ib = 4.3mA). I don't see a maximum Base current listed?

If you had a 9V supply, you would wire 4 LEDs in series with a current limit resistor and set so the string still passes 20mA.
You want to turn the base on as hard as you can to put it into saturation so it only acts like a switch; the transistor will still just pass the current allowed by the rest of the circuit, and the base current will support that current.
Example, 9V supply, LED Vforward 2V, Vce = 0.3V.
Then Voltage across the resister = the voltage left after the all the other voltage sources & sinks: 9- (2+2+2+2) (four LEDS) - 0.3 = 0.7V
If you want to limit to 20mA, apply Ohms law: V = IR, or V/I = R >> then 0.7/0.02 = 35 ohm

So if Vbe = 0.7V, and the arduino can safely put out 20mA, then Rbase = (5-0.7)/0.02 = 215 ohm. 220, 270, 330, are all standard values that would allow plenty of base current to support the 20mA of Ice current.
If the transistor had a gain of 10, so that Ice = 10x Ibe, then only 2mA of base current would needed - give it room for more to ensure the transistor is turned full on and dissipates as little power (as heat) as possible. Power = IV, so 20ma x 0.3V = 6mW dissipated in the transistor.
Same for the resistor P = IV, 20mA * 0.7V = 14mW, so a 1/8W watt rated resistor would be sufficient.

Some hookup examples:

Ah, I didn't read far enough back - RGB LEDs, so you will be driving as a bunch of individual LEDs in parallel.
Well, same calculations still apply.
R = (9V -2V - 0.3V)/0.02A = 335 ohm. 330 is standard, or bump up to 390.
P=IV = 330*.02 = 6.6W, so you will need heftier resistors to get drop the extra voltage.

Or, a DC-DC converter to efficiently turn that 9V into say 2.5 to 3.3V so you can go back to smaller resistors

http://www.murata-ps.com/data/power/okr-t3-w12.pdf available at Digikey.com
Output adjustment shown on page 5.

No, your first post is exactly what I'm doing--- Strings of single-color LEDs in parallel.

I was really only confused about base current, and I think I'm clear on it now. Too much base current is not a problem (provided we don't dissipate too much power). Too little base current will leave the transistor in linear operation, which can cause it to also dissipate too much power. So my base current should be at LEAST 1mA per 20mA of collector current, because the datasheet says the gain of the transistor is at least 30 at Ic = 100mA.

Yes, and if you give it more it won't hurt anything, it will just ensure you are definitely in the saturated region.

Now another question-- what if i wanted to use a CD4017 decade counter to activate my transistors? If the 4017 is driven from the +9v source, I would use 9-0.7 = 8.3V for Vbe, so 8.3V/6mA give Rbase = 1.3kOhm for a 6mA base current, and similar calculations for other base currents.

Those have outputs that go high one at a time. Good for NPN transistors at the cathod end of strings.

6.8mA of drive current at 25C, falling of rapidly as temperature increases.

I'd go with ULN2803 instead, 1 chip vs 8 transister drivers, less mucking around vs 8 parts, and base resistor built in too.