Output Up To 768 PWM Signals From One Arduino

Here’s an Arduino library that will let you drive a very large number of LEDs. [Elco Jacobs], an electrical engineering student, is the author of the library. He has a work-study job that has him helping out others with their electrical projects and he was constantly being solicited for methods to control droves of light emitting diodes. This was the motivation that led him to produce the dazzling 16 RGB LED example seen in the video after the break.

His setup doesn’t use expensive LED drivers, but instead utilizes 595 shift registers which are both common and cheap. He calculates that it is possible to control up to 96 of these shift registers, each driving 8 LEDs, with reasonably satisfying results. This is thanks to his well-optimized code that manages to drive the clock pin of the registers at 1.33 MHz. This optimization is done by writing each command in assembly, which allows him to precisely count the cycles. Each individual pin takes 12-13 cycles to address, totally 9984 cycles at worst when addressing the maximum number of outputs.

[Elco] thinks this is as fast as he can make the routine run, but he is asking for help with testing. If you think you know how to squeeze out a few more cycles, make sure you join in on his forum thread.


46 thoughts on “Output Up To 768 PWM Signals From One Arduino

  1. I agree with Mike.

    But this is quite amazing. It’s not just another “flash a led” project, it’s flash MANY multicolored leds =P. It’s done very well, too.

  2. A quick search on Digikey shows (in onsies) 74HC595 being $0.67. A PIC16F884 in DIP40 provides 35 I/O pins, has an internal clock, and costs $2.56. The ‘595 costs $0.083 per output, while the PIC is $0.073. The micro will allow for higher frequency PWM in large chains and could have a more sensible protocol than just shift-all-bits.

    I think we’re finally at a point where it actually makes more sense to throw a micro at a problem rather than use spartan solutions with discretes!

    1. I’d have to agree – it’s kind of a snowball effect, for example, once upon a time one would use 3 handfuls of discrete logic IC’s to get things done, and then GAL/PAL’s became a more practical solution, and now?… as you point out, a lot of the time it may indeed be simpler/cheaper to go straight for upc chip and be done with it, with all the logic being executed in software. You’ll still end up with the need for output driver discretes, but that’s a fact of life =)

  3. i did 64 rgb leds displaying 32 colors with 595’s a while back, though i did use SPI. problem i ran into was i didn’t want to just run pre-programmed sequences stored on the chip (which was running out of memory fast with all the pwm code). i added a simple serial protocol so i could send patterns/sequences over usb – basically a 8×16 color display. problem was, with all this overhead, the ‘refresh’ for redrawing all the led’s got pretty low with all this other crap going on. i wonder if this guy’s lib wold help out? or if it is so optimized, it would be hard to add in serial comms. hmm..

    1. I just did something similar for
      24x RGB LEDs using software PWM on colour, and high-side hardware PWM on the master brightness.

      – shows 8 of the 24xRGB LED string in action.

      I get a good refresh rate, dot addressable 12-bit colour and 8-bit master brightness – but 20MHz in C was just about it with time avaialble for other functions to not interfere with the display

  4. I think we’re finally at a point where it actually makes more sense to throw a micro at a problem rather than use spartan solutions with discretes!

    FINALLY! Most of the time I would rather grab a general purpose computer than deal with microcontrollers. Something still has to interface between the sensors / inputs / outputs though.

  5. This is pretty clever, living within the constraints of this development environment. As if all the world had was an Arduino and shift registers. It is a puzzle or a challenge, folks. UltraMagnus, the only thing your post does is indicate this is over your head. This is NOT another ‘“flash a LED with an arduino” project’.

    But as others have noted, it would be more practical to use more micros. A PIC slave chip would be cheaper, but not everyone is able to drive PICs from an Arduino. That’s silly.

  6. @chango –

    Since the person is developing for Atmel assembly, PIC comparisons are not very relevant. That’s like answering a PHP question with “use Python”.

    Assuming you meant he could do this with Arduino + “any” slave micro.. that makes sense, but attiny starts at about $2.25 each. 74HC595 is under 50 cents at Jameco, and Qty 10 (which is all that should be considered here) is 39 cents. 10 for $3 on ebay.

    I have to commend elcojacobs on the simplicity and elegance. Even the prototype wiring is very very clean. Nice hack!

  7. I do like ‘595s.

    On the topic of shift registers, I was recently looking for a FET-based counterpart to the ULN2803A Darlington array when I found the TPIC6B595, which seems to be similar to a 74xx595 but with a MOSFET-based output stage that can sustain up to 500mA on a single output or 150mA each for all eight lines on. So, it looks cool, but the price ($1.75 at Newark) does not compare favorably with a separate ‘595 and ULN2803A. Plus, I’ve already got plenty of both of the latter. But if I were trying to cram more parts onto a smaller board, it would probably be worth consideration.

  8. Or use WS2803 LED drivers that incorporate their own shift registers, handle the PWM for you and cost about 49 cents for 18 outpus (less than 3 cents a channel). If you can find them.

  9. What about current consumption? I’m used to the supply current and ground current being a limiting factor when driving LEDs with a 595.

    Lets say you have all eight pins driving 100% duty cycle. If you’ve tuned the resistors for 20 mA for each LED you’ll 160 mA plus what the chip needs to operate when most 74HC595 chips have a supply current maximum rating around 75 mA.

    Are there high-current versions of the 595 (kind of what G was talking about with the WS2801), or is that just an LED driver chip?

  10. The 595 solution works nicely and you can drive any load you want with mosfets. Any DIP packaged thing like TLC will go up in smoke if you want to produce real lightning. I ended up using SPI as mentioned in one post. The problem is really the refresh rate if you want to do animations etc. A good solution I found was to use Binary Code Modulation (!) instead of PWM. That way I could drive about 6 595s with 100Hz refresh rate and have enough cycles for remote control and all the animation stuff.

  11. Here are a few sources I found useful:

    Binary Code Modulation

    Constant current source for driving power leds with 595s

    That constant current source is simple and can drive _very_ large currents as long as you match your power source to be just a bit above the forward voltage of your led(s).

    Hope this helps!

  12. Hi All,

    Someone notified me that my library was blogged here. To answer some of the questions asked so far:
    – I do not write in assembly directly, but I do check the assembly with objdump.exe -S, so that I can check that my C++ code generates the optimal assembly code.
    – I didn’t use SPI. I don’t think it will be faster. The 1.3 MHz clock speed now includes retrieving the dutycycles from memory and comparing each of them with a counter.
    The SPI sends out whole bytes, so you would have to put your bits into bytes before sending them out. Now I write the compare result to the datapin directly.
    – This library is written for Industrial Design students, so the aim was to just set the value in memory and forget about it (pwm generation is hidden in an interrupt). We always have a lot of shift registers in stock, which are free to take. We buy them in bulk.

    -Receiving PWM values from a PC is a next step, because I use this code to build an ambilight of 24 independent RGB LEDS for computer games.

  13. @Elco Jacobs

    First off, very cool project. Regarding SPI – it would be faster than bit-banging the bus. Instead of interrupting to constantly toggle an IO pin, pick a few bytes that have the value you want and let some dedicated hardware hammer it out…

    So, for example, pick:
    01010101, 00110011, or 00001111
    for your 50% PWM. You can do this for any 8-bit combo. With each byte you can have anywhere from 0 to 1 average output with a resolution of 1/8. You can mix and match any combination you want with only a few bytes.

    Example: want average of 33.333% PWM. Some quick math shows that I want 24/72 bits. So, I want 3 bytes of full on and 9 bytes of full off – on average.

    If I were doing this… which I’m not, so I know my comment is only worth how much you paid for it…

    I wouldn’t worry about calculating it, but just set up the basic values (0, 1/8, 2/8, … 1) and just iterate towards an almost correct answer.

    So… say you have a 5-byte buffer. You’re given an odd value (say 42%) and you want to set up your buffer. Note that I pulled these numbers out of my… somewhere. They are not ‘convenient’ numbers.

    Step 1. Pick closest value (3/8 = .375).
    Step 2. Pick value that helps most (will be either 1 above or below). (4/8).
    We are now at 7/16 = .4375.
    Step 3. Pick value that helps most. (3/8).
    We are now at 10/24 = 0.41666
    Step 4. Low again… get the idea, (4/8).
    Now at 14/32 = 0.4375 (note we repeated)
    Step 5. High, pick lower (3/8)
    Now at 17/40 = 0.425.

    So… in a quick for-loop, you calculated out a length-5 buffer that gives you a PWM matching what you want within 1.2% error. Your hardware can now happily chew on that while you get back to doing useful things with your valuable program counter time.

  14. Edit/Addendum:

    For my example I should have re-edited to a 6-bit buffer… Note that the 6th iteration would have happened to been the first non-toggle. You ended up high, so you’d drop a 3/8 again. Getting you down to 0.41666.

    Anyway, you get the idea. Fill your buffer with something resembling the value you want and then move on. When your buffer empties, interrupt your routine to re-fill it.

  15. Just when I thought there was no way I could make a 100 – 200 led light staff I have found my answer! Do you think a standard arduino could handle this? IE with 5 volts or so. I’m still learning a bit so any help would be greatly appreciated!


  16. @Elco Jacobs:
    > – I do not write in assembly directly, but I do check the assembly with objdump.exe -S, so that I can check that my C++ code generates the optimal assembly code.

    That’s how the professionals do it! :) You learn all sorts of things about how to make your C compiler emit the code you want.

  17. This is really nice work but I really think WS2801’s and WS2803’s are much easier guys. You clock out the color levels you want (8 bits per channel), then stop clocking and they latch, and handle their own PWM. They will hold their color for as long as you want with NO further action required from your microcontroller so you can actually use it to do other things. They are used in lots of those “individually-pixel-addressable flexible LED strips” that are floating around.

  18. Using 595’s like this is not exactly a new technique. It’s been done by the christmas light guys for several years now (olsen 595 controller) First time I’ve seen it on an Arduino though.

  19. I was thinking …

    Couldn’t you replace the LEDs with small caps going to ground followed by some transistors … then you could have (total number of outputs / 6) PWM Motor drivers or (total number of outputs / 4) H-Bridges?

  20. You can use the PWM signal for anything.
    I drive 3W RGB LED’s with mosfets.
    I don’t see why you would need caps. Just PWM the motor with a mosfet and put in a freewheeling diode.

    I have a new version which uses the SPI and is more than twice as fast. Will put it online soon.

  21. @Elco to pull the signal line low briefly while the registers are changing … dont want an internal short though 3 transistors … but thats just a precaution I had thought of maybe it isnt as bad as I would think

  22. I have a new SPI version online at elcojacobs.com/shiftpwm

    Got it down from 108 to 43 cycles per register.

    You were right after all. SPI makes it super fast!
    The SPI is set at 4 MHz and computation of the bits overlaps with transfer.

    1. Hi Elco. That is exciting work. I would love to see it. I’m trying to add more PWM ports with fix frequency of 64KHz and variable duty-cycle and I have a hard time being able to create it. It would be great if you can share your findings. Thank you very much.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.