There are more issues with using a 1280. Because the memory is 16 bit wide, extra instructions are added. I can only look at the assembly now, but I'll try to get my hands on a 1280.
Hum, I think you have a misconception about the 1280 Vs 328 chips. Both have the same number of instructions, same memory widths. 1280 just has more memory size in length, not width for flash, sram, eeprom, more I/O pins, more timers, more serial ports, etc. But both use the same AVR 8 bit instruction set.
You are right. The addresses can be 16 bit, because the memory is longer. I was confusing the data and the addresses.
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)+7*2+1 = 1+8*6+14+1 = 64 cycles
(I have not tested it so this might not be completely right but I think you get the idea)
Very nice way to do it. I don't like to use assembly directly in my code, so any idea which c statement would result in a rol instruction?
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:
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:
It's all running off 3.3v rather than 5v.
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.
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.
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.
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.
Hmm - what's the possibility of doing this for PPM and servos?
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.
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.
I just posted I library I wrote to PWM with shift registers. You might want to test it with ShiftBrites. For 16 brightness levels per LED, you could control 768 LED's at 75Hz Less LED's --> more brightness levels.
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.
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.
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+108*N)/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.