PWM Through a 74HC595 Shift Register

OK, I'm a complete novice to electronics, and micro controllers, let's get that straight right from the start. :smiley: Also, I'm new to the board, so if I've posted this in the wrong place I'm very sorry - go easy on me. :-[

I recently bought a Arduino Duemilanove from the very helpful guys over at Oomlaut because I decided that I wanted to learn something new, and maybe try and develop my creative side.

I've been messing around with a few simple projects - probably the usual stuff that a noob like me does to get his hands dirty. My task for this evening was to try and simulate a PWM style brightness control on 8 LEDs driven through a 74HC595 shift register.

OK, first off, the circuit is basically identical to that shown here http://ardx.org/CIRC05 although my layout is slightly different just to get the LEDs in a straight line.

Here is the sketch::

/* ***********************************
 * Pseudo PWM of 8 LEDs through a
 * 74HC595 Bit Shift register
 * By Slugsie - 19th Nov 2009
 * ***********************************/
 
// Setup pin definitions for bit shift register
int dataPin = 2;
int clockPin = 3;
int latchPin = 4;

// Setup array to hold brightnesses for each LED
// Start each LED off with a different brightness
int ledBrightness[] = {0, 1, 2, 3, 4, 0, 1, 2};

// Delay time between each cycle - smaller the better to reduce flicker
int delayTime = 1;

void setup() {
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
}

void loop() {
  // Cycle through the brightness pattern BIT by BIT 256 times
  for (int iLoop = 0; iLoop <= 256; iLoop++) {
    // Stores the BIT pattern for the run
    byte bitPattern = 0;
    // Cycle through each LED
    for (int loopLED = 0; loopLED <=7; loopLED++) {
      bitPattern = bitPattern | getBit(loopLED);
    }
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, bitPattern);
    digitalWrite(latchPin, HIGH);
    incrementPWMPosition();
    delay(delayTime);
  }
  
  // Cycle through brightnesslevels 0 thru 4 for each LED
  for (int loopLED2 = 0; loopLED2 <=7; loopLED2++) {
    ledBrightness[loopLED2]++;
    if (ledBrightness[loopLED2]>4) {
      ledBrightness[loopLED2] = 0;
    }
  }
}

// Increment the position within the brightnessPattern[] from 0 thru 7
int pwmPosition = 0;

void incrementPWMPosition() {
  pwmPosition++;
  if (pwmPosition > 7) {
    pwmPosition = 0;
  }
}

// Pattern of BITs that we use to determine if an LED gets power this cycle or not
// The more 1 BITs in the pattern, the brighter the LED will be overall.
// The 1's are spread as much as possible in order to try and reduce flicker.
byte brightnessPattern[] = {B00000000, B00000001, B00010001, B01010101, B11111111};

                            
// BIT mask used to look at an individual BIT in the brightnessPattern[],
// also to set the individual BIT for the LED
byte bitMask[] = {B00000001, B00000010, B00000100, B00001000,
                  B00010000, B00100000, B01000000, B10000000};

byte getBit(int led) {
  if (brightnessPattern[ledBrightness[led]] & bitMask[pwmPosition]) {
    return bitMask[led];
  }
  else {
    return 0;
  }
}

I did originally have 9 brightnessPattern[]s, but the difference between 6, 7, and 8 bits set was so negligible that I decided it would be better to trim it down a bit.

I'd like to hear any comments on the code. It's been about 20 years since I did any C so I'm very rusty in that area (although I am a software developer by trade so that helps a bit).

My ultimate aim for this is probably to have three 74HC595s running, each one controlling one colour of an RGB LED - although I need to source some of them cheaply first.

If your looking to do PWM via the Shift Register .. here is some code that allows you to configure the number of Shift Registers you have .. then use pwmWrite(port,value) .. with value being from 0 to 255.

The code is setup to handle multiplexing .. but configured to not use it.

The code is setup to handle multiple SR's .. but configured to use one.

I also have an RGBCommonLED class that integrates with this .. allowing you to set hue using 0-255 value (instead of 0-360), which makes to make it easier to set / use with a byte value (important when you have so few bytes to work with).

You can find that by searching my other posts .. or I can provide a fuller example with that if you like .. just did not want to post something too complex right off .. often just not used / loaded in that case.

Finding Parts:
Found Shift Registers on e-bay .. $18 shipped for 50 of them.
Found $25/$28 shipped for 100 RGB Common anode. Ones shipped from china took longer .. but they included the right resistors with labels and the order was RGB .. not RBG as in some cases. Make sure you ask for the resistors with your order .. and say for 5v or you may not get them (except the china sources seem to be good with that).

/**************************************************************
 * Name    : PWM Control of Shift Registers
 * By      : Joseph Francis
 * 
 * Open Source
 * 
 * Date    : 20 Nov, 2009                                      
 * Version : 1.0                                               
 * Notes   : Code for using a 74HC595 Shift Register PWM           
 ****************************************************************/
#include <TimerOne.h>

//-- Update the TICKER_MAX and _STEP to 16 16 or 32 8 or 64 4 for performance vs colors available
#define TICKER_MAX 64
#define TICKER_STEP 4

//-- May have to tweak the timer based on number SR's
int timerDelay = 350;

//--- Pin connected to ST_CP of 74HC595
int latchPin = 10;
//--- Pin connected to SH_CP of 74HC595
int clockPin = 13;
//--- Pin connected to DS of 74HC595
int dataPin = 11;

//--- Used for faster latching
int latchPinPORTB = latchPin - 8;

//======================

//number of multiplex - set to zero for no multiplexing in this example
int groundMax = 0;  

//number of Shift Registers - set to 1 for this example
int srcount = 1;

//--- ready for 10 SR's
byte srvals[] ={
  0,0,0,0,0,0,0,0,0,0
};

int ticker = 0;

int groundAt = 255;

//--- setup for 10 SR's .. no multiplexing ...
// actually should be SR Count * 8 * multiplexing count (1 based) .. this is just an example for easy usage
byte srPins[80] = {
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0
};

void pwmWrite(int port, byte val){
  srPins[port] = val;  
}


void iProcess(){
  int srtot = 8 * srcount;

  ticker++;
  if( ticker > TICKER_MAX ) 
    ticker = 0;
  int myPos = ticker * TICKER_STEP;

  groundAt++;
  if( groundAt > groundMax )
    groundAt = 0;

  int myLev = 0;

  for ( int iSR = 0 ; iSR < srcount ; iSR++){
    byte currVal = 0;

    for( int i = 0 ; i < 8 ; i++ ){
      myLev = 0;
      if (srPins[(i+(8*iSR))+(groundAt*srtot)] > myPos)
        myLev = 1;
      bitWrite(currVal,i,myLev );
    }

    srvals[iSR] = currVal;
  }

  latchOff();

  for ( int iSR = srcount - 1 ; iSR >= 0 ; iSR--){
    spi_transfer(srvals[iSR]);
  }

  latchOn();

}

void latchOn(){
  bitSet(PORTB,latchPinPORTB);
}

void latchOff(){
  bitClear(PORTB,latchPinPORTB);
}

void setupSPI(){
  byte clr;
  SPCR |= ( (1<<SPE) | (1<<MSTR) ); // enable SPI as master
  //SPCR |= ( (1<<SPR1) | (1<<SPR0) ); // set prescaler bits
  SPCR &= ~( (1<<SPR1) | (1<<SPR0) ); // clear prescaler bits
  clr=SPSR; // clear SPI status reg
  clr=SPDR; // clear SPI data reg
  SPSR |= (1<<SPI2X); // set prescaler bits
  //SPSR &= ~(1<<SPI2X); // clear prescaler bits

  delay(10); 
}


byte spi_transfer(byte data)
{
  SPDR = data;                    // Start the transmission
  loop_until_bit_is_set(SPSR, SPIF); 
  return SPDR;                    // return the received byte, we don't need that
}

void setup() {
  randomSeed(analogRead(0));
  Serial.begin(9600);
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  digitalWrite(latchPin,LOW);
  digitalWrite(dataPin,LOW);
  digitalWrite(clockPin,LOW);

  setupSPI();
  Timer1.initialize(timerDelay); // Timer for updating pwm pins
  Timer1.attachInterrupt(iProcess);

}

void allTo(int val, int delayval){
  int srtot = 8 * srcount;
  for (int i = 0 ; i < srtot ; i++){
    pwmWrite(i,val);
    if( delayval > 0 )
      delay(delayval);
  } 
}


void allOff(){
  allTo(0,0);
}
void allOn(){
  allTo(255,0);
}

void loop(){
  int currPort = 0;
  allOff();

  //--- Some really basic stuff .. just to run through some simple tasks
  for( int iPort = 0 ; iPort < 3 ; iPort++ ){
    delay(200);
    for (int i = 0 ; i < 255 ; i++){
      //--- set RGB LED 1 color x
      pwmWrite(iPort,i);
      //--- set RGB LED 2 color x without a reset
      pwmWrite(iPort+3,i);
      delay(10);
    } 
    //-- reset only the first LED .. makeing the second one turn all white by the end of third run
    //* simple way to show some color mixing
    pwmWrite(iPort,0);

  }

}

Thanks Marklar, I'm not in the least bit surprised that there is a much better and more accurate way to achieve a similar goal. I've only had a chance to quickly look at the code, and I don't fully understand it all yet. I'll run it on my Arduino tonight and see if I can begin to understand how it does what it does. :slight_smile: I haven't looked at interrupts and timers at all yet, so that's something new for me to get to grips with. :slight_smile:

I have a relatively easy supplier of the 74HC595s as there is an electronics warehouse next to my office which is fairly competitive on price, but they strangely don't seem to stock RGB LEDs.

Anyway, I'll see if I can get to grips with your code, try it out with a few simple circuits/patterns on single colour LEDs and take it from there.

Thanks again. :smiley:

Hi Slugsie

Welcome to Arduino land. Kiss your job, your girlfriend, your familie and everything else you know goodbye :slight_smile:

For a first project this is quite advanced, looking forward to see more stuff from you

Thanks MikMo. I'm single by choice (bloody women, eating into my play time and spending my hard earned cash!), I'd love to kiss my job goodbye, and the less I'm bothering my daughter the better as far as she is concerned.

This wasn't my first project. After the usual blinking LED stuff, I created an electronic dice from scratch just to learn a few basics. Worked quite well I thought, then I found a few schematics and sketches for other peoples and I quietly tucked it away. [smiley=cry.gif] I then created a digital thermometer that displayed the temp on a pair of 7 segment LED displays. Again I was quite chuffed with the results.

I'm also working on plans for a very long duration time-lapse camera thingy, I want it to run as unattended as possible for up to a year. First step is that I need to find a cheap digital camera I can hack up a bit. Currently scouring ebay. :wink:

I created a cheap 3D Camera using a couple of Canon PowerShot A560s (lots of models work) with the CHDK hack and a super simple to make USB cord with a button on it hooked to 3 AAA's (in this case).

I am sure the Arduino audience has other cooler ways .. that is just my experience.

Here is the link to where I started.
http://stereo.jpn.org/eng/sdm/index.htm

You could use the same setup on one camera and simply have the Arduino kick the 4.5v you need to make it go 'click' for you however you like.

Note: I was able to use this to create some really cool 3D HDR, just 3D and just HDR shots - may want to try that too if your into photography at all.

Good luck and have fun!

marklar? south park?

marklar (translated - yes)

Hi,

You defined Ports 10, 11, 13 for running the code. I tried to use ports 2,3,4 but didn't get it to work. Is this somehow achievable?

Thank you very much!

no - you need the PWM pins used to stay on PWM capable pins from what I understand

Hello people
I am trying to understand the code (it is almost what I need) but I am not really sure what the setupSPI function is doing :expressionless:
any ideas?

It configures the hardware involved:

transfer speed (prescaler bits), master role (sender), MSB / LSB first ...

The datasheet goes into every detail you may need.

Thanx for the reply, madworm!

You mean the datasheet of the arduino or the 74HC595?
I start to understand what happens but I am quite new in port manipulation etc..

No, it's a bit more complicated than that.

I was referring to the datasheet of the ATmega168/328 microcontroller.

The PDF is about 22MB and you'll find everything you need starting on page 167.

oops indeed!
Well I am going to stick with the setupSPI it seems that it does the job :slight_smile:
Thnx!

@lyan - Below is a cleaner version of the code I added to this post.

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1278251011

This version can be easily expanded and should be more easily understood.

Hope it helps

@marklar Thnx! the comments really help in the code cleaner version!
In any case I am not going to use RGB LED's but I want to control 16 small vibrating motors (the one that the mobile phones integrate) and with a small test I did last night it seems it works!
sth more, in the code u were suggesting a different chip to drive LEDs what would that be?

thank you very much!

The TLC5940 / TLC5941 are the chips I was eluding to. I has 4096 steps of pwm control, vs none with the 595 (hence the need to use software pwm).

Thanks for the tip!
cheers :slight_smile:

Now I am trying to use marklar's code to programm the arduino and Max/Msp to to control the individual channels. Any ideas on that?