ShiftPWM support topic. Latest update: Schematics, high power LED's, LED strips

Update August 9 2012:
I finally had the time to completely rewrite the documentation. You can now find schematics for normal RGB LED's, LED strips and high power LED's on www.elcojacobs.com/shiftpwm.

I also updated the library to include load balancing and made the initialization much cleaner. Switching between SPI and no-SPI has become a lot easier. And I added Arduino Leonardo compatibility.

Old stuff:

Update May 9 2012:
Support for NOT using the SPI pin. 2.5x slower, but the freedom to use your SPI port for something else.
Download here: https://github.com/elcojacobs/ShiftPWM/downloads
Extract files to libraries/ShiftPWM/ and open one of the examples.

Update May 8 2012:
Led setup flexibility: ShiftPWM support topic. Latest update: Schematics, high power LED's, LED strips - #218 by system - LEDs and Multiplexing - Arduino Forum

Based on some great suggestions that I got in this thread I have updated ShiftPWM to use the SPI bus.
That took down the number of clock cycles per shift register from 108 to 43.

I have created a page for ShiftPWM on my website www.elcojacobs.com/shiftpwm where you can find more info and download the newest version.

Even Older stuff:

First some pictures to get your attention:

I study Electrical Engineering, but have a job for 8 hours a week at the Electronics Atelier of the Industrial Design department of the Technical University of Eindhoven. My job is to help the ID students with the electronics in their projects. A request I often get is to control many, many LED's. TLC5940 were often used, but they are very expensive compared to shift registers (74HC595).

For shift registers you have to write the PWM code in software and drive the control lines of the registers fast enough.
Especially doing it fast enough is difficult, but I think I have reached the optimum at 13 clock cycles (worst case) to update each pin. This drives the clock line of the registers at 1.33 MHz.
The interrupt function is optimized to be as fast as possible, but the other functions to set duty cycles and configure the library are as easy to use as possible.

I put the library in a class as much as possible without slowing down the code too much. There are many tricks needed to make the library easy to use and configurable, but still as fast as possible. I have written a lot of commentary in the source files to explain these tricks, but I might write a blog post on writing fast Arduino code if there is much demand.

Features of the library:

  • Control the duty cycle of many PWM outputs in a single shift register chain.

  • Easily configure frequency, number of registers and number of brightness levels.

  • Outputs can be inverted for common anode RGB LED's

  • Function to print information on timer settings and interrupt load

  • Switches to timer2 if timer1 is in use by the servo library

  • Checks for input to functions that is out of range and prints error messages to the serial port

  • Can be placed in the libraries directory of Arduino

  • Includes an example (see video) which can be accessed by file->examples

  • Example includes HSV to RGB function for easy color shifting with RGB LED's.

  • The load of the interrupt function on your program can be calculated as follows:

  • L = Interrupt frequency * interrupt duration / clock frequency
  • L = F*(Bmax+1)(96+108N)/F_CPU
  • Quick reference for load:
  • 3 registers 255 maxBrightness 75Hz load = 0.50
  • 6 registers 100 maxBrightness 75Hz load = 0.35
  • 24 registers 50 maxBrightness 75Hz load = 0.64
  • 48 registers 32 maxBrightness 75Hz load = 0.81
  • 96 registers 16 maxBrightness 75Hz load = 0.83

Functions:

  • ShiftPWM.Start(int ledFrequency, int max_Brightness) Enable ShiftPWM with desired frequency and brightness levels

  • ShiftPWM.SetAmountOfRegisters(int newAmount) Set or change the amount of output registers. Can be changed at runtime.

  • ShiftPWM.PrintInterruptLoad() Print information on timer usage, frequencies and interrupt load

  • ShiftPWM.OneByOne() Fade in and fade out all outputs slowly

  • ShiftPWM.OneByOneFast() Fade in and fade out all outputs fast

  • ShiftPWM.SetOne(int pin, unsigned char value) Set the duty cycle of one output

  • ShiftPWM.SetAll(unsigned char value) Set all outputs to the same duty cycle

  • ShiftPWM.SetGroupOf2(int group, unsigned char v0, unsigned char v1);
    ShiftPWM.SetGroupOf3(int group, unsigned char v0, unsigned char v1, unsigned char v2);
    ShiftPWM.SetGroupOf4(int group, unsigned char v0, unsigned char v1, unsigned char v2, unsigned char v3);
    ShiftPWM.SetGroupOf5(int group, unsigned char v0, unsigned char v1, unsigned char v2, unsigned char v3, unsigned char v4);
    --> Set a group of outputs to the given values. SetGroupOf3 is useful for RGB LED's. Each LED will be a group.

** Very technical stuff alert **
I have optimized the C code to compile to the most efficient instructions available. The interrupt function loops over all shift registers and for each output executes the following instructions:

  • Write clock output low (cbi, 2 clock cycles)
  • Load the duty cycle for this pin from memory (ldd, 2 clock cycles)
  • Compare the duty cycle with a counter (cp, 2 clock cycles)
  • Branch on compare result (brcs, 2 clock cycles)
  • Write the datapin high or low (cbi or sbi, 2 clock cycles)
  • Jump instruction (1 clock cycle, not always executed)
  • Write clock output high (sbi, 2 clock cycles)
    This takes 12 or 13 clockcyles per output and I don't think it can be done any faster.
    ** End technical stuff **

So unzip the attachment in your Arduino library directory and try it. The circuit is exactly the same as the ShiftOut example, but leave out the latch pin capacitor.

This library is not tested with the atmega1280, if you use it please report back to me.

I would really appreciate some feedback on usability and possible bugs. If someone could test this with ShiftBrites would be great.

If you would like to include this library as a standard library for Arduino, I would be honored.

Video of ShiftPWM_example1:

ShiftPWM.zip (14.2 KB)

Well done,

No comments right now, but this is good stuff for an article on the playground, where you can explain the working (design decissions) in more detail.

This could be perfect for a project I am trying to complete, where I am hoping to fade multiple LEDs slowly (have enough LEDs that I am at 6 registers) on and off. I am very new to writing code. Do you have any example code that would work in this regard using your library? Not using RGB LEDs, and I am unfamiliar with commands dealing with frequency/loads and LEDs... Appreciate any guidance!

There is an example in the library. Extract the zip file in the library directory of arduino, restart arduino and go to file->examples->ShiftPWM to open it.

Only the rainbow example is specifically for RGB LED's, the rest of the examples should work directly with your current circuit.
Only a few things to keep in mind: select the correct pins, keep wires from arduino to first shift register short, and keep load under 0.9 (see the formula).

The load is also printed by the printInterruptLoad() function, so it is automatically calculated for you.

If you open the example, things will be pretty straight forward.

Hmm - what's the possibility of doing this for PPM and servos? :slight_smile:

cr0sh:
Hmm - what's the possibility of doing this for PPM and servos? :slight_smile:

I thought about it, let me do a few calculations.
The design would be a bit different: the interrupt frequency will be much lower at and the pulse for the servo would be generated in one interrupt. The problem is squeezing enough resolution in the 1 ms time difference that defines the position.

Typical servo: pulses of 1.0-2.0 ms, repeat every 20 ms.
13 clock cycles for updating one pin.
Number of clock cycles in one millisecond = 16000
Resolution = 16000/(13 * number of servos) =
24.6 steps for 50 servos
12.3 steps for 100 servos

So you would quickly lose a lot of resolution, but it is possible with a few alterations.

elcojacobs:
I would really appreciate some feedback on usability and possible bugs. If someone could test this with ShiftBrites would be great.

What would you want to see done with ShiftBrites and this library? As far as I can tell there would be no compatibility, the ShiftBrite PWM management is internal to each module and does 10bit PWM per color already.

macegr:
What would you want to see done with ShiftBrites and this library? As far as I can tell there would be no compatibility, the ShiftBrite PWM management is internal to each module and does 10bit PWM per color already.

Ah, sorry, I didn't really look into shiftbrites and just assumed they were LEDs with shift registers, without internal pwm control.

Nice, will try this out sometime; will be curious to see how it compares to using TLC5940's.

Very slick. I'm selling a couple of high current driver boards based around the 595 (http://www.logos-electro.com/ard-srg-ips4x4-1/ , http://www.logos-electro.com/1x3-srg-ips6-1/) and I'd like to include a link to your code once you have it up in a public place like GitHub.

PS -- have you tried daisy chaining 96 shift registers at once? AIUI, the fanout limit on the Arduino pins without buffers is about 50.

I am also curious about the extremely long chain, I currently have a 34 shift register chain with decent wire lengths between each register that picks up other signals. Is there a simple way to buffer/reduce noise?

Hi. I was looking and felt that is a great job.
I also do this kind of serialization for my steper motor control, because the microsteper CHIP used for serial input at 11 bits(no 8, no 16, 11 bits).
But if i was you, surelly I would use the SPI interface.
Your Arduino dont has one? this way you just set the byte and trigger the start flag! just 2 instructions and 4 cicles!
Surelly Arduino al so have. With DATA and CLOCK you can simplify this a lot!
and get much more speed.

As mentioned by the previous poster, why not use the SPI? The HC595 is perfectly happy being driven by SPI, and on an Arduino you can clock the bits out at 8MHz.

I was about to make something like this. THANK YOU so much for the effort of doing it and posting for all of us.
I'm thinking about a present to express my appreciation to someone and wanted to have some fancy LED routines on a cast of their name.
I was already thinking about shift registers and getting them to work fast enough to have PWM on all the LEDs.
This just embodies what I was about to do in a nice package!

Once again, many thanks!!

To answer the question that is asked the most: why no 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 data pin directly.

The SPI would only increase speed by combining these instructions:
clear clock pin, write data pin, set clock pin. Which take 6 clockcycles.
The SPI at 8 MHz would be able to do this in 2 clockcycles, but would require a bit write to the byte you want to send out, which takes 2 clock cycles.

The maximum gain wil be 2 clockcycles per pin, but I doubt you can squeeze it out. It also removes the freedom to choose pins.

To answer another question:
I did not try 96 shift registers in a chain. With the printInterruptLoad() function I can check that the arduino is able to generate the signals, but I don't know if you would run into problems with long load lines. Maybe a buffer (for example two inverters) in series for the clock line would be required.

On second thought, I might try SPI. It's just a few changes.
The SPI can output the byte while the avr is already calculating the next byte.
I will probably try this next week.

Hi Elco,

Thanks very much for sharing this work!

I've tried the example sketch on a 1280-based mega (you asked if someone could try it with a 1280 in your original post?) and found initially it wouldn't compile. To get it to work I (rightly or wrongly) changed line 90 of "pins_arduino_compile_time.h" from:

volatile uint16_t * const port_to_output_PGM_ct[] = {

to:

volatile uint8_t * const port_to_output_PGM_ct[] = {

I'm not sure if this was correct, because when I run the sketch all three LEDs in the package (I'm currently only using a single RGB LED) seem to stay at a constant brightness. The circuit I've thrown together uses a single 595 and connects:

Arduino Pin 595 Pin
8 14 (SER)
9 11 (SRCLK)
10 12 (RCLK)

It's all running off 3.3v rather than 5v.

Any ideas?

Many thanks,

Mark

Hey,

I think you could do it much faster with SPI.
There's a little trick when calculating the bitvalues
You can calc every bit with only 5 cycles and without branching
you can just use the carry flag result from the cp instruction and shift it to a register, like this:

ldd value
cp value, counter
rol register

(I think the carry flag is set when the counter is greater than the value, so this is exactly what you need for pwm)
So after executing this 8 times you got 8 values packed to one byte. you can now send this with the spi.

when you put your PWM Values in an array you can generate a whole byte with this code:

ldi r20, 8
label:

ld r21, X+
cp r21, counter
rol r22

dec r20
brne label

at this point you have a calculated a whole byte (in reg22) with 1+8*(2+2+1+1)+72+1 = 1+86+14+1 = 64 cycles

(I have not tested it so this might not be completely right but I think you get the idea)

MexWave:
Hi Elco,

Thanks very much for sharing this work!

I've tried the example sketch on a 1280-based mega (you asked if someone could try it with a 1280 in your original post?) and found initially it wouldn't compile. To get it to work I (rightly or wrongly) changed line 90 of "pins_arduino_compile_time.h" from:

volatile uint16_t * const port_to_output_PGM_ct[] = {

to:

volatile uint8_t * const port_to_output_PGM_ct[] = {

I'm not sure if this was correct, because when I run the sketch all three LEDs in the package (I'm currently only using a single RGB LED) seem to stay at a constant brightness. The circuit I've thrown together uses a single 595 and connects:

Arduino Pin 595 Pin
8 14 (SER)
9 11 (SRCLK)
10 12 (RCLK)

It's all running off 3.3v rather than 5v.

Any ideas?

Many thanks,

Mark

Ah, thanks for trying this. I think the correction you made is right and that it should be unint8.
The problem could come from running on 3.3V. The atmega1280 has to run on 8 MHz. Please be sure that your code reflects this (F_CPU = 8E6). The calculations for the interrupt load have to be done with 8MHz as well. The built in function uses F_CPU for this.

What does the code report on the serial port?