/* 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 #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