It took quite some doing to get easy to use PWM control working with the 595 shift registers. I figured I'd pass along a slimmed down version of the application that just implements the PWM control via a timer1 process.

The idea is to add "pwm pins" using daisy chained 595s and then to be able to use them just like normal pwm pins, not having to deal with the low level actions. To this end I use a srPins array to hold the value and a pwmWrite function to write a 0 to 255 value for that pin.

This code should be easily reused / modified to run a number of shift registers.

Using SPI allow for many more shift registers to be used with PWM control without flickering than a standard shiftOut mechansim.

/************************************************************** * Name : 74HC595 PWM SPI * By : Joseph Francis * * Open Source * * Date : 04 Jul, 2010 * Version : 1.3 * Notes : Software PWM using daisy chained 74HC595 shift registers via SPI * * I wrote this code in a way that uses a srPins array to hold the byte pwm value for each pin. * Similar to doing an analogWrite, you can do a pwmWrite call to a "virtual" SR pin or just set srPins[x]. * A timer process runs in the background to do PWM control, so the loop can concentrate on the process. * * Using this technique I have pwm controlled over 40 rgb leds with plenty of room for patterns, etc. * * While I would suggest using another chip to drive LEDs, the 595s provide an economical way to get started. ****************************************************************/

#include <TimerOne.h>

//--- Update TICKER_PWM_LEVELS to control how many levels you want// usually 32 levels of brightess is good (32K colors)#define TICKER_PWM_LEVELS 32#define TICKER_STEP 256/TICKER_PWM_LEVELS

//--- Update TIMER_DELAY to control how fast this runs. // As long as you don't see flicker ... a higher number (slower) is better to assure // the loop has room to do stuff#define TIMER_DELAY 280#define SR_COUNT 6

//--- Used in iProcess to control the software PWM cycleint ticker = 0;

//--- Holds a 0 to 255 PWM value used to set the value of each SR pinbyte srPins[SR_COUNT*8];

//--- Function used to set a shift register pin with a PWM value// can also just set srPins .. using this function allows you to map to pins if you need// a virtual mappingvoid pwmWrite(int port, byte val){ srPins[port] = val; }

//--- This process is run by the timer and does the PWM controlvoid iProcess(){ //--- Create a temporary array of bytes to hold shift register values in byte srVals[SR_COUNT]; //--- increment our ticker ticker++; //--- if our ticker level cycles, restart if( ticker > TICKER_PWM_LEVELS ) ticker = 0; //--- get ticker as a 0 to 255 value, so we can always use the same data regardless of actual PWM levels int myPos = ticker * TICKER_STEP;

//--- Loop through all bits in the shift register (8 pin for the 595's) for (int i = 0 ; i < 8; i++ ){ int myLev = 0; //--- Loop through all shift registers and set the bit on or off for (int iSR = 0 ; iSR < SR_COUNT ; iSR++){ //--- Start with the bit off myLev = 0; //--- If the value in the sr pin related to this SR/Byte is over the current pwm value // then turn the bit on if (srPins[i+(iSR*8)] > myPos) myLev = 1; //--- Write the bit into the SR byte array bitWrite(srVals[iSR],i,myLev ); }

Excellent, thanks for sharing this. I'm going to be getting some STP04CM05 4-bit shift-register/high current LED drivers in the mail soon, and will try to adapt this code for that application. They also feature cascading so I'm guessing the only trick will be changing any 8-bit references in the code to 4-bits instead.

If this works I will be saving a lot of $ on hardware and a lot of head-banging on circuit design! Before I found the STP04CM05 the only cost-effective solution was a slew of TI PWM multiplexers, hex converters, transistors, and current regulators.

Actually I think you may get away with using this with no modification to the 8 bit vs 4 bit. The cheep and easy route may be to just cut the number you use in the SR_COUNT variable in 1/2. So in your case if you have 8 shift registers cascaded, you would set SR_COUNT to 4 to equal the same number of pins.

Try that first and see if everything else just works for you (it may).

The reason is because you will most likely end up wanting to combine 4 bits of data together in pairs to equal a byte, but having a even number of 4 bit SRs makes it work just like an 8 bit SR.

Definitely, a partner and I are working on using an Arduino to control a series of these ICs which are driving 12 high power 3W RGB LEDs mounted in polycarbonate tubes, and a series of RGB strobes in addition. We should have MIDI sync with Ableton Live and full manual control as well. We're trying to get it finished for a music festival in Raleigh next month, I'll be sure to post some videos when it's completed.

So we completed our project in time for its debut at Hopscotch Music Festival, which was Raleigh's first major music festival. We provided the stage lighting for Motor Skills, whose performance was well-received.

The system is as described above; a Processing sketch handles MIDI input and generates the pattern data, which is communicated over serial to the Arduino. Using the SoftPWM code we control a custom PCB with 12 of the STP04CM05 ICs and all the other requisite circuit components. These are connected to the light bars via CAT5 cables through a 12-port patch panel. Finally the light bars consist of a high-power 3W RGB LED, some optics, and other various parts for optimum beam spreading and heat management.

This software PWM implementation worked excellently for the job, so thanks again. I probably wouldn't have attempted soft PWM if you hadn't posted this. Using your code on an Arduino with an Atmega328, we were able to get 128 brightness levels per channel before seeing noticeable flicker, which works well except for the lowest brightness levels. I think a resolution of at least 8x higher is necessary for better dimming, logarithmic brightness, and good color mixing at low brightness. I doubt that could ever be squeezed out of the Atmega328, however.

Perhaps you could formalize your code into a library for better accessibility to people getting started? It seems fairly optimized already what with all the bitwise operations.

Thank you very much for letting me know the code helped and for posting your video. Nice work!

I was thinking of using a library when I created the code but was not sure what to put into "library form". Most of my libraries are class based and it seemed to over complicate the implementation / usage to create a class for this operation. Maybe I'll just post the code in the playground so it won't get lost in forum land.

I currently do not use this type of process anymore so I figured I put up the cleaner code before it went into the big box with all my shift registers and raw LEDs. Now that I see it is useful, posting it on the playground is a good idea.

I'll have to re-look at that chip you are using after seeing your results. I may need similar caps to run the SMD strips since the TLCs are giving me a headache.

Thanks again for responding with your results and for posting the video of your nice light show.

Two different drive methods: PWM - just workks with 32 PWM levels, 6 registers and 90 HZ BAM - works for lot more with 256 PWM levels

Todo: 1) Precalculate the data for 595 always when PWM levels are changed. This should speed up the interrupt. 2) Change the interrupt so that it is called only when needed by changing the interrupt time on every call. Dunno if this is necessary, the interrupt exists just after a few ifs if nothing need to be updated.

//// Global variables//// holds the current pwm values of pins to be sent by interruptvolatile unsigned char pwmValues[NUM_CHANNELS];volatile byte bamLookup[BAM_STEPS*NUM_SHIFT_REGS]; // precalculate the bytes to send every time the PWM values changevolatile byte pwmLookup[PWM_STEPS*NUM_SHIFT_REGS];// precalculate the bytes to send to 595 for pwm

/* Precalculate the bytes to send for every PWM cycle. Note that this is not fast either!

// Activate the interrupt // calculate how many times per sec the ISR ISR is called // to get the requested pwm frequency Timer3.initialize(1000000/PWM_FREQ/PWM_STEPS); // Timer for updating pwm pins Timer3.attachInterrupt(iUpdateBAM3);

Bit Angle modulation. This version has a problem: When the slice hits and we do send data, the function takes too long to execute. The result is that all ticks are not of equal length and the brightness depends also on how many bits are 1. The result is that 7 is brighter than 9 and 15 brighter than 16. Not good for dimming!

Tomppu, this is great. The speed limitations were the biggest difficulty with the previous version of the code.

I was controlling 30 channels, with their values being updated over serial at about 50hz. So I would still be calling precalcBamBytes() around 1500 times per second. Is it possible to only call precalcBamBytes() once after updating all 30 channels with setChannel()? That would reduce it to about 50 calls per second.

Also, what is the difference between the iUpdateBam 1, 2, and 3 functions? It seems that version 3 is the superior version. Is there any reason to leave the other two in the code, possibly just to provide an example?

Finally, is it possible to increase the PWM resolution above 256 levels? Possibly 1024 levels, or higher? This would make something like brightness calibration with a logarithmic curve (gamma correction) possible.

I will give your code a try, if I don't see any bugs then it will go into production. If you know of or notice any bugs please let us know in this thread, I would also be willing to help finalize the code and make this into a library like ShiftOut so that more people can take advantage of it. That is, after I wrap my head around bit angle manipulation...

Thanks for the compliments . I did a lot of digging on the internet about Bit Angle Modulation and i found something but nothing ready for Arduino. You had nice working code base where to start experimenting.

The code there is work in progress and i suggest we play with it a bit and then pack it as a library for others to use and improve. A lot of people seem to like driving leds with Arduino.

I even tried the propeller chip that packs a nice punch with 8 cores but found learning the spin language too akward and "specialised" compared to standard c++.

I also tried TLC5940 but they cost money, and cannot drive hight currents so real room lighting is out of question. I got them working and burned quite few of them .

The best was to use cheap 595 and try to run them with software. This is also simple and easily understandable. But PWM is too processor intensive. Even BAM took too much processor without the precalculation of the bits.

I also think that there should be at least 256 levels. Otherwise the steps are noticable with low intensities. 10 bits would be nice.

Feel free to try addning more levels, just speed up the interrupt 4 times and count to 1024 and do the same stuff. Should still work.

I also found a working solution for driving the high power leds with 2 resistors, a transistor and a fet. A simple circuit that limits the current for the led. It can handle huge currents, several A, no problem. Only downside is that the fet costs about 0.5-1eur / piece. If somebody could come up with something that does not need the FET.

Then the functions:

1st try: BAM1 without precalculation. My version is too slow, it does not get out of the interrupt fast enough.

2nd try: BAM2 preconstruct the bytes to be sent. Recalculate only when dutys change. Nice but still some flicker when fading. You can find the reason from google.

3rd Try. BAM3 precalculate AND reorder and split the bits 7 and 8 to smaller 32 tick pieces to make transition form say 127 to 128 smoother. Still needs some improvement. Have to think a bit

4th: PWM2 interrupt works fine but the precalculation is too slow for my taste. About 8000 iterations with 48 channels and 256 levels. Optimise?

TODO:-get rid of the flicker in BAM3 when fading.

-Maybe optimise the precaculation loops. Ideas?

-include a gammacorrection lookup table the make fades smooth.

-improve the interrupt in BAM so that the interrupt is not entered regularly but only when needeed. I think though that it gets out of there fast enought when nothing needs to be done.