PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

ShiftPWM.h 9.7KB

3 anos atrás
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. ShiftPWM.h - Library for Arduino to PWM many outputs using shift registers
  3. Copyright (c) 2011-2012 Elco Jacobs, www.elcojacobs.com
  4. All right reserved.
  5. This library is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU Lesser General Public
  7. License as published by the Free Software Foundation; either
  8. version 2.1 of the License, or (at your option) any later version.
  9. This library is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. Lesser General Public License for more details.
  13. You should have received a copy of the GNU Lesser General Public
  14. License along with this library; if not, write to the Free Software
  15. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. */
  17. #ifndef ShiftPWM_H
  18. #define ShiftPWM_H
  19. #include "pins_arduino_compile_time.h" // My own version of pins arduino, which does not define the arrays in program memory
  20. #include <Arduino.h>
  21. #include "CShiftPWM.h"
  22. // These should be defined in the file where ShiftPWM.h is included.
  23. extern const int ShiftPWM_latchPin;
  24. extern const bool ShiftPWM_invertOutputs;
  25. extern const bool ShiftPWM_balanceLoad;
  26. // The ShiftPWM object is created in the header file, instead of defining it as extern here and creating it in the cpp file.
  27. // If the ShiftPWM object is created in the cpp file, it is separately compiled with the library.
  28. // The compiler cannot treat it as constant and cannot optimize well: it will generate many memory accesses in the interrupt function.
  29. #if defined(SHIFTPWM_USE_TIMER2)
  30. #if !defined(OCR2A)
  31. #error "The avr you are using does not have a timer2"
  32. #endif
  33. #elif defined(SHIFTPWM_USE_TIMER3)
  34. #if !defined(OCR3A)
  35. #error "The avr you are using does not have a timer3"
  36. #endif
  37. #endif
  38. #ifndef SHIFTPWM_NOSPI
  39. // Use SPI
  40. #if defined(SHIFTPWM_USE_TIMER3)
  41. CShiftPWM ShiftPWM(3,false,ShiftPWM_latchPin,MOSI,SCK);
  42. #elif defined(SHIFTPWM_USE_TIMER2)
  43. CShiftPWM ShiftPWM(2,false,ShiftPWM_latchPin,MOSI,SCK);
  44. #else
  45. CShiftPWM ShiftPWM(1,false,ShiftPWM_latchPin,MOSI,SCK);
  46. #endif
  47. #else
  48. // Don't use SPI
  49. extern const int ShiftPWM_clockPin;
  50. extern const int ShiftPWM_dataPin;
  51. #if defined(SHIFTPWM_USE_TIMER3)
  52. CShiftPWM ShiftPWM(3,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
  53. #elif defined(SHIFTPWM_USE_TIMER2)
  54. CShiftPWM ShiftPWM(2,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
  55. #else
  56. CShiftPWM ShiftPWM(1,true,ShiftPWM_latchPin,ShiftPWM_dataPin,ShiftPWM_clockPin);
  57. #endif
  58. #endif
  59. // The macro below uses 3 instructions per pin to generate the byte to transfer with SPI
  60. // Retreive duty cycle setting from memory (ldd, 2 clockcycles)
  61. // Compare with the counter (cp, 1 clockcycle) --> result is stored in carry
  62. // Use the rotate over carry right to shift the compare result into the byte. (1 clockcycle).
  63. #define add_one_pin_to_byte(sendbyte, counter, ledPtr) \
  64. { \
  65. unsigned char pwmval=*ledPtr; \
  66. asm volatile ("cp %0, %1" : /* No outputs */ : "r" (counter), "r" (pwmval): ); \
  67. asm volatile ("ror %0" : "+r" (sendbyte) : "r" (sendbyte) : ); \
  68. }
  69. // The inline function below uses normal output pins to send one bit to the SPI port.
  70. // This function is used in the noSPI mode and is useful if you need the SPI port for something else.
  71. // It is a lot 2.5x slower than the SPI version.
  72. static inline void pwm_output_one_pin(volatile uint8_t * const clockPort, volatile uint8_t * const dataPort,\
  73. const uint8_t clockBit, const uint8_t dataBit, \
  74. unsigned char counter, unsigned char * ledPtr){
  75. #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
  76. bitClear(*clockPort, clockBit);
  77. if(ShiftPWM_invertOutputs){
  78. bitWrite(*dataPort, dataBit, *(ledPtr)<=counter );
  79. }
  80. else{
  81. bitWrite(*dataPort, dataBit, *(ledPtr)>counter );
  82. }
  83. bitSet(*clockPort, clockBit);
  84. #else
  85. digitalWriteFast(clockBit, LOW);
  86. #if F_CPU >= 120000000
  87. asm("nop");
  88. #endif
  89. #if F_CPU >= 180000000
  90. asm("nop");
  91. #endif
  92. if(ShiftPWM_invertOutputs){
  93. digitalWriteFast(dataBit, *(ledPtr)<=counter );
  94. }
  95. else{
  96. digitalWriteFast(dataBit, *(ledPtr)>counter );
  97. }
  98. #if F_CPU >= 96000000
  99. asm("nop");
  100. #endif
  101. #if F_CPU >= 144000000
  102. asm("nop");
  103. #endif
  104. #if F_CPU >= 192000000
  105. asm("nop");
  106. #endif
  107. digitalWriteFast(clockBit, HIGH);
  108. #if F_CPU >= 48000000
  109. asm("nop");
  110. #endif
  111. #if F_CPU >= 72000000
  112. asm("nop");
  113. #endif
  114. #if F_CPU >= 96000000
  115. asm("nop");
  116. #endif
  117. #if F_CPU >= 120000000
  118. asm("nop");
  119. #endif
  120. #if F_CPU >= 144000000
  121. asm("nop");
  122. #endif
  123. #if F_CPU >= 168000000
  124. asm("nop");
  125. #endif
  126. #if F_CPU >= 180000000
  127. asm("nop");
  128. #endif
  129. #if F_CPU >= 192000000
  130. asm("nop");
  131. #endif
  132. #if F_CPU >= 216000000
  133. asm("nop");
  134. #endif
  135. #endif
  136. }
  137. #if defined(__AVR__)
  138. static inline void ShiftPWM_handleInterrupt(void){
  139. #else
  140. void ShiftPWM_handleInterrupt(void){
  141. #endif
  142. sei(); //enable interrupt nesting to prevent disturbing other interrupt functions (servo's for example).
  143. // Look up which bit of which output register corresponds to the pin.
  144. // This should be constant, so the compiler can optimize this code away and use sbi and cbi instructions
  145. // The compiler only knows this if this function is compiled in the same file as the pin setting.
  146. // That is the reason the full funcion is in the header file, instead of only the prototype.
  147. // If this function is defined in cpp files of the library, it is compiled seperately from the main file.
  148. // The compiler does not recognize the pins/ports as constant and sbi and cbi instructions cannot be used.
  149. #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
  150. volatile uint8_t * const latchPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_latchPin]];
  151. const uint8_t latchBit = digital_pin_to_bit_PGM_ct[ShiftPWM_latchPin];
  152. #endif
  153. #ifdef SHIFTPWM_NOSPI
  154. volatile uint8_t * const clockPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_clockPin]];
  155. volatile uint8_t * const dataPort = port_to_output_PGM_ct[digital_pin_to_port_PGM_ct[ShiftPWM_dataPin]];
  156. const uint8_t clockBit = digital_pin_to_bit_PGM_ct[ShiftPWM_clockPin];
  157. const uint8_t dataBit = digital_pin_to_bit_PGM_ct[ShiftPWM_dataPin];
  158. #endif
  159. // Define a pointer that will be used to access the values for each output.
  160. // Let it point one past the last value, because it is decreased before it is used.
  161. unsigned char * ledPtr=&ShiftPWM.m_PWMValues[ShiftPWM.m_amountOfOutputs];
  162. // Write shift register latch clock low
  163. #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
  164. bitClear(*latchPort, latchBit);
  165. #else
  166. digitalWriteFast(ShiftPWM_latchPin, LOW);
  167. #endif
  168. unsigned char counter = ShiftPWM.m_counter;
  169. #ifndef SHIFTPWM_NOSPI
  170. //Use SPI to send out all bits
  171. SPDR = 0; // write bogus bit to the SPI, because in the loop there is a receive before send.
  172. for(unsigned char i = ShiftPWM.m_amountOfRegisters; i>0;--i){ // do a whole shift register at once. This unrolls the loop for extra speed
  173. unsigned char sendbyte; // no need to initialize, all bits are replaced
  174. if(ShiftPWM_balanceLoad){
  175. counter +=8; // distribute the load by using a shifted counter per shift register
  176. }
  177. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  178. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  179. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  180. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  181. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  182. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  183. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  184. add_one_pin_to_byte(sendbyte, counter, --ledPtr);
  185. while (!(SPSR & _BV(SPIF))); // wait for last send to finish and retreive answer. Retreive must be done, otherwise the SPI will not work.
  186. if(ShiftPWM_invertOutputs){
  187. sendbyte = ~sendbyte; // Invert the byte if needed.
  188. }
  189. SPDR = sendbyte; // Send the byte to the SPI
  190. }
  191. while (!(SPSR & _BV(SPIF))); // wait for last send to complete.
  192. #else
  193. //Use port manipulation to send out all bits
  194. for(unsigned char i = ShiftPWM.m_amountOfRegisters; i>0;--i){ // do one shift register at a time. This unrolls the loop for extra speed
  195. if(ShiftPWM_balanceLoad){
  196. counter +=8; // distribute the load by using a shifted counter per shift register
  197. }
  198. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr); // This takes 12 or 13 clockcycles
  199. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  200. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  201. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  202. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  203. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  204. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  205. pwm_output_one_pin(clockPort, dataPort, clockBit, dataBit, counter, --ledPtr);
  206. }
  207. #endif
  208. // Write shift register latch clock high
  209. #ifndef SHIFTPWM_USE_DIGITALWRITEFAST
  210. bitSet(*latchPort, latchBit);
  211. #else
  212. digitalWriteFast(ShiftPWM_latchPin, HIGH);
  213. #endif
  214. if(ShiftPWM.m_counter<ShiftPWM.m_maxBrightness){
  215. ShiftPWM.m_counter++; // Increase the counter
  216. }
  217. else{
  218. ShiftPWM.m_counter=0; // Reset counter if it maximum brightness has been reached
  219. }
  220. }
  221. // See table 11-1 for the interrupt vectors */
  222. #if defined(__AVR__)
  223. #if defined(SHIFTPWM_USE_TIMER3)
  224. //Install the Interrupt Service Routine (ISR) for Timer3 compare and match A.
  225. ISR(TIMER3_COMPA_vect) {
  226. ShiftPWM_handleInterrupt();
  227. }
  228. #elif defined(SHIFTPWM_USE_TIMER2)
  229. //Install the Interrupt Service Routine (ISR) for Timer1 compare and match A.
  230. ISR(TIMER2_COMPA_vect) {
  231. ShiftPWM_handleInterrupt();
  232. }
  233. #else
  234. //Install the Interrupt Service Routine (ISR) for Timer1 compare and match A.
  235. ISR(TIMER1_COMPA_vect) {
  236. ShiftPWM_handleInterrupt();
  237. }
  238. #endif
  239. #endif
  240. // #endif for include once.
  241. #endif