heatbed_pwm.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #include <avr/io.h>
  2. #include <avr/interrupt.h>
  3. #include "io_atmega2560.h"
  4. // All this is about silencing the heat bed, as it behaves like a loudspeaker.
  5. // Basically, we want the PWM heating switched at 30Hz (or so) which is a well ballanced
  6. // frequency for both power supply units (i.e. both PSUs are reasonably silent).
  7. // The only trouble is the rising or falling edge of bed heating - that creates an audible click.
  8. // This audible click may be suppressed by making the rising or falling edge NOT sharp.
  9. // Of course, making non-sharp edges in digital technology is not easy, but there is a solution.
  10. // It is possible to do a fast PWM sequence with duty starting from 0 to 255.
  11. // Doing this at higher frequency than the bed "loudspeaker" can handle makes the click barely audible.
  12. // Technically:
  13. // timer0 is set to fast PWM mode at 62.5kHz (timer0 is linked to the bed heating pin) (zero prescaler)
  14. // To keep the bed switching at 30Hz - we don't want the PWM running at 62kHz all the time
  15. // since it would burn the heatbed's MOSFET:
  16. // 16MHz/256 levels of PWM duty gives us 62.5kHz
  17. // 62.5kHz/256 gives ~244Hz, that is still too fast - 244/8 gives ~30Hz, that's what we need
  18. // So the automaton runs atop of inner 8 (or 16) cycles.
  19. // The finite automaton is running in the ISR(TIMER0_OVF_vect)
  20. // 2019-08-14 update: the original algorithm worked very well, however there were 2 regressions:
  21. // 1. 62kHz ISR requires considerable amount of processing power,
  22. // USB transfer speed dropped by 20%, which was most notable when doing short G-code segments.
  23. // 2. Some users reported TLed PSU started clicking when running at 120V/60Hz.
  24. // This looks like the original algorithm didn't maintain base PWM 30Hz, but only 15Hz
  25. // To address both issues, there is an improved approach based on the idea of leveraging
  26. // different CLK prescalers in some automaton states - i.e. when holding LOW or HIGH on the output pin,
  27. // we don't have to clock 62kHz, but we can increase the CLK prescaler for these states to 8 (or even 64).
  28. // That shall result in the ISR not being called that much resulting in regained performance
  29. // Theoretically this is relatively easy, however one must be very carefull handling the AVR's timer
  30. // control registers correctly, especially setting them in a correct order.
  31. // Some registers are double buffered, some changes are applied in next cycles etc.
  32. // The biggest problem was with the CLK prescaler itself - this circuit is shared among almost all timers,
  33. // we don't want to reset the prescaler counted value when transiting among automaton states.
  34. // Resetting the prescaler would make the PWM more precise, right now there are temporal segments
  35. // of variable period ranging from 0 to 7 62kHz ticks - that's logical, the timer must "sync"
  36. // to the new slower CLK after setting the slower prescaler value.
  37. // In our application, this isn't any significant problem and may be ignored.
  38. // Doing changes in timer's registers non-correctly results in artefacts on the output pin
  39. // - it can toggle unnoticed, which will result in bed clicking again.
  40. // That's why there are special transition states ZERO_TO_RISE and ONE_TO_FALL, which enable the
  41. // counter change its operation atomically and without artefacts on the output pin.
  42. // The resulting signal on the output pin was checked with an osciloscope.
  43. // If there are any change requirements in the future, the signal must be checked with an osciloscope again,
  44. // ad-hoc changes may completely screw things up!
  45. ///! Definition off finite automaton states
  46. enum class States : uint8_t {
  47. ZERO_START = 0,///< entry point of the automaton - reads the soft_pwm_bed value for the next whole PWM cycle
  48. ZERO, ///< steady 0 (OFF), no change for the whole period
  49. ZERO_TO_RISE, ///< metastate allowing the timer change its state atomically without artefacts on the output pin
  50. RISE, ///< 16 fast PWM cycles with increasing duty up to steady ON
  51. RISE_TO_ONE, ///< metastate allowing the timer change its state atomically without artefacts on the output pin
  52. ONE, ///< steady 1 (ON), no change for the whole period
  53. ONE_TO_FALL, ///< metastate allowing the timer change its state atomically without artefacts on the output pin
  54. FALL, ///< 16 fast PWM cycles with decreasing duty down to steady OFF
  55. FALL_TO_ZERO ///< metastate allowing the timer change its state atomically without artefacts on the output pin
  56. };
  57. ///! Inner states of the finite automaton
  58. static States state = States::ZERO_START;
  59. bool bedPWMDisabled = 0;
  60. ///! Fast PWM counter is used in the RISE and FALL states (62.5kHz)
  61. static uint8_t slowCounter = 0;
  62. ///! Slow PWM counter is used in the ZERO and ONE states (62.5kHz/8 or 64)
  63. static uint8_t fastCounter = 0;
  64. ///! PWM counter for the whole cycle - a cache for soft_pwm_bed
  65. static uint8_t pwm = 0;
  66. ///! The slow PWM duty for the next 30Hz cycle
  67. ///! Set in the whole firmware at various places
  68. extern unsigned char soft_pwm_bed;
  69. /// fastMax - how many fast PWM steps to do in RISE and FALL states
  70. /// 16 is a good compromise between silenced bed ("smooth" edges)
  71. /// and not burning the switching MOSFET
  72. static const uint8_t fastMax = 16;
  73. /// Scaler 16->256 for fast PWM
  74. static const uint8_t fastShift = 4;
  75. /// Increment slow PWM counter by slowInc every ZERO or ONE state
  76. /// This allows for fine-tuning the basic PWM switching frequency
  77. /// A possible further optimization - use a 64 prescaler (instead of 8)
  78. /// increment slowCounter by 1
  79. /// but use less bits of soft PWM - something like soft_pwm_bed >> 2
  80. /// that may further reduce the CPU cycles required by the bed heating automaton
  81. /// Due to the nature of bed heating the reduced PID precision may not be a major issue, however doing 8x less ISR(timer0_ovf) may significantly improve the performance
  82. static const uint8_t slowInc = 1;
  83. ISR(TIMER0_OVF_vect) // timer compare interrupt service routine
  84. {
  85. switch(state){
  86. case States::ZERO_START:
  87. if (bedPWMDisabled) break;
  88. pwm = soft_pwm_bed << 1;// expecting soft_pwm_bed to be 7bit!
  89. if( pwm != 0 ){
  90. state = States::ZERO; // do nothing, let it tick once again after the 30Hz period
  91. }
  92. break;
  93. case States::ZERO: // end of state ZERO - we'll either stay in ZERO or change to RISE
  94. // In any case update our cache of pwm value for the next whole cycle from soft_pwm_bed
  95. slowCounter += slowInc; // this does software timer_clk/256 or less (depends on slowInc)
  96. if( slowCounter > pwm ){
  97. return;
  98. } // otherwise moving towards RISE
  99. state = States::ZERO_TO_RISE; // and finalize the change in a transitional state RISE0
  100. break;
  101. // even though it may look like the ZERO state may be glued together with the ZERO_TO_RISE, don't do it
  102. // the timer must tick once more in order to get rid of occasional output pin toggles.
  103. case States::ZERO_TO_RISE: // special state for handling transition between prescalers and switching inverted->non-inverted fast-PWM without toggling the output pin.
  104. // It must be done in consequent steps, otherwise the pin will get flipped up and down during one PWM cycle.
  105. // Also beware of the correct sequence of the following timer control registers initialization - it really matters!
  106. state = States::RISE; // prepare for standard RISE cycles
  107. fastCounter = fastMax - 1;// we'll do 16-1 cycles of RISE
  108. TCNT0 = 255; // force overflow on the next clock cycle
  109. TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz
  110. TCCR0A &= ~(1 << COM0B0); // Clear OC0B on Compare Match, set OC0B at BOTTOM (non-inverting mode)
  111. break;
  112. case States::RISE:
  113. OCR0B = (fastMax - fastCounter) << fastShift;
  114. if( fastCounter ){
  115. --fastCounter;
  116. } else { // end of RISE cycles, changing into state ONE
  117. state = States::RISE_TO_ONE;
  118. OCR0B = 255; // full duty
  119. TCNT0 = 254; // make the timer overflow in the next cycle
  120. // @@TODO these constants are still subject to investigation
  121. }
  122. break;
  123. case States::RISE_TO_ONE:
  124. state = States::ONE;
  125. OCR0B = 255; // full duty
  126. TCNT0 = 255; // make the timer overflow in the next cycle
  127. TCCR0B = (1 << CS01); // change prescaler to 8, i.e. 7.8kHz
  128. break;
  129. case States::ONE: // state ONE - we'll either stay in ONE or change to FALL
  130. OCR0B = 255;
  131. slowCounter += slowInc; // this does software timer_clk/256 or less
  132. if (!bedPWMDisabled){ //disable heating as soon as possible
  133. if( slowCounter < pwm ){
  134. return;
  135. }
  136. if( (soft_pwm_bed << 1) >= (255 - slowInc - 1) ){ //@@TODO simplify & explain
  137. // if slowInc==2, soft_pwm == 251 will be the first to do short drops to zero. 252 will keep full heating
  138. return; // want full duty for the next ONE cycle again - so keep on heating and just wait for the next timer ovf
  139. }
  140. }
  141. else if (pwm > 200){ //if duty cycle is high and BED PWM is disabled keep heater on. Prevents overcooling
  142. return;
  143. }
  144. // otherwise moving towards FALL
  145. // @@TODO it looks like ONE_TO_FALL isn't necessary, there are no artefacts at all
  146. state = States::ONE;//_TO_FALL;
  147. // TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz
  148. // break;
  149. // case States::ONE_TO_FALL:
  150. // OCR0B = 255; // zero duty
  151. state=States::FALL;
  152. fastCounter = fastMax - 1;// we'll do 16-1 cycles of RISE
  153. TCNT0 = 255; // force overflow on the next clock cycle
  154. TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz
  155. // must switch to inverting mode already here, because it takes a whole PWM cycle and it would make a "1" at the end of this pwm cycle
  156. // COM0B1 remains set both in inverting and non-inverting mode
  157. TCCR0A |= (1 << COM0B0); // inverting mode
  158. break;
  159. case States::FALL:
  160. OCR0B = (fastMax - fastCounter) << fastShift; // this is the same as in RISE, because now we are setting the zero part of duty due to inverting mode
  161. //TCCR0A |= (1 << COM0B0); // already set in ONE_TO_FALL
  162. if( fastCounter ){
  163. --fastCounter;
  164. } else { // end of FALL cycles, changing into state ZERO
  165. state = States::FALL_TO_ZERO;
  166. TCNT0 = 128; //@@TODO again - need to wait long enough to propagate the timer state changes
  167. OCR0B = 255;
  168. }
  169. break;
  170. case States::FALL_TO_ZERO:
  171. state = States::ZERO_START; // go to read new soft_pwm_bed value for the next cycle
  172. TCNT0 = 128;
  173. OCR0B = 255;
  174. TCCR0B = (1 << CS01); // change prescaler to 8, i.e. 7.8kHz
  175. break;
  176. }
  177. }