|
- /*
- ShiftPWM.h - Library for Arduino to PWM many outputs using shift registers
- Copyright (c) 2011-2012 Elco Jacobs, www.elcojacobs.com
- All right reserved.
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-
- #ifndef ShiftPWM_H
- #define ShiftPWM_H
-
- #include "pins_arduino_compile_time.h" // My own version of pins arduino, which does not define the arrays in program memory
- #include <Arduino.h>
- #include "CShiftPWM.h"
-
-
- // These should be defined in the file where ShiftPWM.h is included.
- extern const int ShiftPWM_latchPin;
- extern const bool ShiftPWM_invertOutputs;
- extern const bool ShiftPWM_balanceLoad;
-
- // The ShiftPWM object is created in the header file, instead of defining it as extern here and creating it in the cpp file.
- // 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.
-
- #if defined(SHIFTPWM_USE_TIMER2)
- #if !defined(OCR2A)
- #error "The avr you are using does not have a timer2"
- #endif
- #elif defined(SHIFTPWM_USE_TIMER3)
- #if !defined(OCR3A)
- #error "The avr you are using does not have a timer3"
- #endif
- #endif
-
-
- #ifndef SHIFTPWM_NOSPI
- // Use SPI
- #if defined(SHIFTPWM_USE_TIMER3)
- CShiftPWM ShiftPWM(3,false,ShiftPWM_latchPin,MOSI,SCK);
- #elif defined(SHIFTPWM_USE_TIMER2)
- CShiftPWM ShiftPWM(2,false,ShiftPWM_latchPin,MOSI,SCK);
- #else
- CShiftPWM ShiftPWM(1,false,ShiftPWM_latchPin,MOSI,SCK);
- #endif
- #else
- // Don't use SPI
- extern const int ShiftPWM_clockPin;
- extern const int ShiftPWM_dataPin;
- #if defined(SHIFTPWM_USE_TIMER3)
- CShiftPWM ShiftPWM(3,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
- #elif defined(SHIFTPWM_USE_TIMER2)
- CShiftPWM ShiftPWM(2,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
- #else
- CShiftPWM ShiftPWM(1,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
- #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; \
- 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){
- #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
- bitClear(*clockPort, clockBit);
- if(ShiftPWM_invertOutputs){
- bitWrite(*dataPort, dataBit, *(ledPtr)<=counter );
- }
- else{
- bitWrite(*dataPort, dataBit, *(ledPtr)>counter );
- }
- bitSet(*clockPort, clockBit);
- #else
- digitalWriteFast(clockBit, LOW);
- #if F_CPU >= 120000000
- asm("nop");
- #endif
- #if F_CPU >= 180000000
- asm("nop");
- #endif
- if(ShiftPWM_invertOutputs){
- digitalWriteFast(dataBit, *(ledPtr)<=counter );
- }
- else{
- digitalWriteFast(dataBit, *(ledPtr)>counter );
- }
- #if F_CPU >= 96000000
- asm("nop");
- #endif
- #if F_CPU >= 144000000
- asm("nop");
- #endif
- #if F_CPU >= 192000000
- asm("nop");
- #endif
- digitalWriteFast(clockBit, HIGH);
- #if F_CPU >= 48000000
- asm("nop");
- #endif
- #if F_CPU >= 72000000
- asm("nop");
- #endif
- #if F_CPU >= 96000000
- asm("nop");
- #endif
- #if F_CPU >= 120000000
- asm("nop");
- #endif
- #if F_CPU >= 144000000
- asm("nop");
- #endif
- #if F_CPU >= 168000000
- asm("nop");
- #endif
- #if F_CPU >= 180000000
- asm("nop");
- #endif
- #if F_CPU >= 192000000
- asm("nop");
- #endif
- #if F_CPU >= 216000000
- asm("nop");
- #endif
- #endif
- }
-
- #if defined(__AVR__)
- static inline void ShiftPWM_handleInterrupt(void){
- #else
- void ShiftPWM_handleInterrupt(void){
- #endif
- sei(); //enable interrupt nesting to prevent disturbing other interrupt functions (servo's for example).
-
- // Look up which bit of which output register corresponds to the pin.
- // This should be constant, so the compiler can optimize this code away and use sbi and cbi instructions
- // The compiler only knows this if this function is compiled in the same file as the pin setting.
- // That is the reason the full funcion is in the header file, instead of only the prototype.
- // If this function is defined in cpp files of the library, it is compiled seperately from the main file.
- // The compiler does not recognize the pins/ports as constant and sbi and cbi instructions cannot be used.
-
-
- #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
- 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];
- #endif
-
- #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.
-
- unsigned char * ledPtr=&ShiftPWM.m_PWMValues[ShiftPWM.m_amountOfOutputs];
-
- // Write shift register latch clock low
- #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
- bitClear(*latchPort, latchBit);
- #else
- digitalWriteFast(ShiftPWM_latchPin, LOW);
- #endif
-
- 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
- unsigned char sendbyte; // no need to initialize, all bits are replaced
- if(ShiftPWM_balanceLoad){
- counter +=8; // distribute the load by using a shifted counter per shift register
- }
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
-
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
- add_one_pin_to_byte(sendbyte, counter, --ledPtr);
-
- while (!(SPSR & _BV(SPIF))); // wait for last send to finish and retreive answer. Retreive must be done, otherwise the SPI will not work.
- if(ShiftPWM_invertOutputs){
- sendbyte = ~sendbyte; // Invert the byte if needed.
- }
- 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
- if(ShiftPWM_balanceLoad){
- counter +=8; // distribute the load by using a shifted counter per shift register
- }
- 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
- #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
- bitSet(*latchPort, latchBit);
- #else
- digitalWriteFast(ShiftPWM_latchPin, HIGH);
- #endif
-
- if(ShiftPWM.m_counter<ShiftPWM.m_maxBrightness){
- ShiftPWM.m_counter++; // Increase the counter
- }
- else{
- ShiftPWM.m_counter=0; // Reset counter if it maximum brightness has been reached
- }
- }
-
- // See table 11-1 for the interrupt vectors */
- #if defined(__AVR__)
- #if defined(SHIFTPWM_USE_TIMER3)
- //Install the Interrupt Service Routine (ISR) for Timer3 compare and match A.
- ISR(TIMER3_COMPA_vect) {
- ShiftPWM_handleInterrupt();
- }
- #elif defined(SHIFTPWM_USE_TIMER2)
- //Install the Interrupt Service Routine (ISR) for Timer1 compare and match A.
- ISR(TIMER2_COMPA_vect) {
- ShiftPWM_handleInterrupt();
- }
- #else
- //Install the Interrupt Service Routine (ISR) for Timer1 compare and match A.
- ISR(TIMER1_COMPA_vect) {
- ShiftPWM_handleInterrupt();
- }
- #endif
- #endif
-
- // #endif for include once.
- #endif
|