Skip to content

Commit

Permalink
Added support for using ShiftPWM without the SPI port.
Browse files Browse the repository at this point in the history
  • Loading branch information
elcojacobs committed May 9, 2012
1 parent e50b025 commit 9143b8f
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 62 deletions.
10 changes: 8 additions & 2 deletions CShiftPWM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

extern const bool ShiftPWM_invertOutputs;

CShiftPWM::CShiftPWM(int timerInUse) : m_timer(timerInUse){ //Timer is set in initializer list, because it is const
CShiftPWM::CShiftPWM(int timerInUse, bool noSPI) : m_timer(timerInUse), m_noSPI(noSPI){ // Constants are set in initializer list
m_ledFrequency = 0;
m_maxBrightness = 0;
m_amountOfRegisters = 0;
Expand Down Expand Up @@ -246,7 +246,13 @@ void CShiftPWM::SetPinGrouping(int grouping){
bool CShiftPWM::LoadNotTooHigh(void){
// This function calculates if the interrupt load would become higher than 0.9 and prints an error if it would.
// This is with inverted outputs, which is worst case. Without inverting, it would be 42 per register.
float interruptDuration = 97+43* (float) m_amountOfRegisters;
float interruptDuration;
if(m_noSPI){
interruptDuration = 96+108*(float) m_amountOfRegisters;
}
else{
interruptDuration = 97+43* (float) m_amountOfRegisters;
}
float interruptFrequency = (float) m_ledFrequency* (float) m_maxBrightness;
float load = interruptDuration*interruptFrequency/F_CPU;

Expand Down
4 changes: 3 additions & 1 deletion CShiftPWM.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

class CShiftPWM{
public:
CShiftPWM(const int timerInUse);
CShiftPWM(const int timerInUse, bool noSPI);
~CShiftPWM();

public:
Expand Down Expand Up @@ -59,7 +59,9 @@ class CShiftPWM{
bool LoadNotTooHigh(void);

const int m_timer;
const bool m_noSPI;
int m_prescaler;


public:
int m_ledFrequency;
Expand Down
107 changes: 55 additions & 52 deletions ShiftPWM.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,6 @@ License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

/*
This library is intended to control the outputs of a chain of shift registers.
It uses software PWM to control the duty cycle of all shift register outputs.
This code is optimized for speed: the interrupt duration is minimal, so that
the load of the interrupt on your program is minimal (or the amount of registers
and brightness levels is maximal. The SPI is used to send data to the shift regisers,
while the CPU is already calculating the next byte to send out.
Timer1 (16 bit) is used, unless it is already in use by the servo library.
Then, timer2 (8 bit) will be configured as the interrupt timer, with a prescaler for
the highest possible precision.
A timer interrupt is configured by ShiftPWM.Start(pwmFrequency,maxBrightness)
The interrupt frequency is set to pwmFrequency * (maxBrightness+1)
Each interrupt all duty cycles are compared to the counter and the corresponding pin
is written 1 or 0 based on the result. Then the counter is increased by one.
The duration of the interrupt depends on the number of shift registers (N).
T = 97 + 43*N (worst case)
The load of the interrupt function on your program can be calculated:
L = Interrupt frequency * interrupt duration / clock frequency
L = F*(Bmax+1)*(97+43*N)/F_CPU
The duration also depends on the number of brightness levels, but the impact is minimal.
The following functions are used:
ShiftPWM.Start(int ledFrequency, int max_Brightness) Enable ShiftPWM with desired frequency and brightness levels
SetAmountOfRegisters(int newAmount) Set or change the amount of output registers. Can be changed at runtime.
PrintInterruptLoad(void) Print information on timer usage, frequencies and interrupt load
void OneByOne(void) Fade in and fade out all outputs slowly
void OneByOneFast(void) Fade in and fade out all outputs fast
void SetOne(int pin, unsigned char value) Set the duty cycle of one output
void SetAll(unsigned char value) Set all outputs to the same duty cycle
SetGroupOf2(int group, unsigned char v0, unsigned char v1);
SetGroupOf3(int group, unsigned char v0, unsigned char v1, unsigned char v2);
SetGroupOf4(int group, unsigned char v0, unsigned char v1, unsigned char v2, unsigned char v3);
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.
*/

#ifndef ShiftPWM_H
#define ShiftPWM_H
Expand All @@ -85,24 +40,50 @@ extern const bool ShiftPWM_invertOutputs;
// If the ShiftPWM object is created in the cpp file, it is separately compiled with the library.
// The compiler cannot treat it as constant and cannot optimize well: it will generate many memory accesses in the interrupt function.

#ifndef _useTimer1 //This is defined in Servo.h
CShiftPWM ShiftPWM(1);
#ifndef SHIFTPWM_NOSPI
// Use SPI
#ifndef _useTimer1 //This is defined in Servo.h
CShiftPWM ShiftPWM(1,false);
#else
CShiftPWM ShiftPWM(2,false); // if timer1 is in use by servo, use timer 2
#endif
#else
CShiftPWM ShiftPWM(2); // if timer1 is in use by servo, use timer 2
// Don't use SPI
extern const int ShiftPWM_clockPin;
extern const int ShiftPWM_dataPin;
#ifndef _useTimer1 //This is defined in Servo.h
CShiftPWM ShiftPWM(1,true);
#else
CShiftPWM ShiftPWM(2,true); // if timer1 is in use by servo, use timer 2
#endif
#endif


// The macro below uses 3 instructions per pin to generate the byte to transfer with SPI
// Retreive duty cycle setting from memory (ldd, 2 clockcycles)
// Compare with the counter (cp, 1 clockcycle) --> result is stored in carry
// Use the rotate over carry right to shift the compare result into the byte. (1 clockcycle).
#define add_one_pin_to_byte(sendbyte, counter, ledPtr) \
{ \
unsigned char pwmval=*ledPtr; \
unsigned char pwmval=*ledPtr; \
asm volatile ("cp %0, %1" : /* No outputs */ : "r" (counter), "r" (pwmval): ); \
asm volatile ("ror %0" : "+r" (sendbyte) : "r" (sendbyte) : ); \
}

// The inline function below uses normal output pins to send one bit to the SPI port.
// This function is used in the noSPI mode and is useful if you need the SPI port for something else.
// It is a lot 2.5x slower than the SPI version.
static inline void pwm_output_one_pin(volatile uint8_t * const clockPort, volatile uint8_t * const dataPort,\
const uint8_t clockBit, const uint8_t dataBit, \
unsigned char counter, unsigned char * ledPtr){
bitClear(*clockPort, clockBit);
if(ShiftPWM_invertOutputs){
bitWrite(*dataPort, dataBit, *(ledPtr)<=counter );
}
else{
bitWrite(*dataPort, dataBit, *(ledPtr)>counter );
}
bitSet(*clockPort, clockBit);
}

static inline void ShiftPWM_handleInterrupt(void){
sei(); //enable interrupt nesting to prevent disturbing other interrupt functions (servo's for example).
Expand All @@ -118,6 +99,12 @@ static inline void ShiftPWM_handleInterrupt(void){
volatile uint8_t * const latchPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_latchPin]];
const uint8_t latchBit = digital_pin_to_bit_PGM_ct[ShiftPWM_latchPin];

#ifdef SHIFTPWM_NOSPI
volatile uint8_t * const clockPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_clockPin]];
volatile uint8_t * const dataPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_dataPin]];
const uint8_t clockBit = digital_pin_to_bit_PGM_ct[ShiftPWM_clockPin];
const uint8_t dataBit = digital_pin_to_bit_PGM_ct[ShiftPWM_dataPin];
#endif

// Define a pointer that will be used to access the values for each output.
// Let it point one past the last value, because it is decreased before it is used.
Expand All @@ -127,8 +114,11 @@ static inline void ShiftPWM_handleInterrupt(void){
// Write shift register latch clock low
bitClear(*latchPort, latchBit);
unsigned char counter = ShiftPWM.m_counter;

#ifndef SHIFTPWM_NOSPI
//Use SPI to send out all bits
SPDR = 0; // write bogus bit to the SPI, because in the loop there is a receive before send.
for(unsigned char i =ShiftPWM.m_amountOfRegisters; i>0;--i){ // do a whole shift register at once. This unrolls the loop for extra speed
for(unsigned char i = ShiftPWM.m_amountOfRegisters; i>0;--i){ // do a whole shift register at once. This unrolls the loop for extra speed
unsigned char sendbyte; // no need to initialize, all bits are replaced

add_one_pin_to_byte(sendbyte, counter, --ledPtr);
Expand All @@ -148,7 +138,20 @@ static inline void ShiftPWM_handleInterrupt(void){
SPDR = sendbyte; // Send the byte to the SPI
}
while (!(SPSR & _BV(SPIF))); // wait for last send to complete.

#else
//Use port manipulation to send out all bits
for(unsigned char i = ShiftPWM.m_amountOfRegisters; i>0;--i){ // do one shift register at a time. This unrolls the loop for extra speed
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr); // This takes 12 or 13 clockcycles
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
}
#endif

// Write shift register latch clock high
bitSet(*latchPort, latchBit);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@
* Debug information for wrong input to functions is also send to the serial port,
* so check the serial port when you run into problems.
*
* ShiftPWM v1.05, (c) Elco Jacobs, May 2012.
* ShiftPWM v1.1, (c) Elco Jacobs, May 2012.
*
*****************************************************************************/
//#include <Servo.h>

//#include <Servo.h> <-- If you include Servo.h, which uses timer1, ShiftPWM will automatically switch to timer2
#include <SPI.h>

// Clock and data pins are pins from the hardware SPI, you cannot choose them yourself.
Expand All @@ -69,7 +70,7 @@
const int ShiftPWM_latchPin=8;

// If your LED's turn on if the pin is low, set this to 1, otherwise set it to 0.
const bool ShiftPWM_invertOutputs = 0;
const bool ShiftPWM_invertOutputs = 1;

#include <ShiftPWM.h> // include ShiftPWM.h after setting the pins!

Expand Down Expand Up @@ -186,7 +187,6 @@ void loop()
while(abs(peak-prevPeak)<5){
peak = random(numRGBleds); // pick a new peak value that differs at least 5 from previous peak
}
Serial.println(peak);
// animate to new top
while(currentLevel!=peak){
if(currentLevel<peak){
Expand Down Expand Up @@ -227,6 +227,4 @@ void rgbLedRainbow(int numRGBLeds, int delayVal, int numCycles, int rainbowWidth
delay(delayVal); // this delay value determines the speed of hue shift
}
}
}


}
Loading

0 comments on commit 9143b8f

Please sign in to comment.