heatbed_pwm.cpp 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. ///! Definition off finite automaton states
  21. enum class States : uint8_t {
  22. ZERO = 0,
  23. RISE = 1,
  24. ONE = 2,
  25. FALL = 3
  26. };
  27. ///! State table for the inner part of the finite automaton
  28. ///! Basically it specifies what shall happen if the outer automaton is requesting setting the heat pin to 0 (OFF) or 1 (ON)
  29. ///! ZERO: steady 0 (OFF), no change for the whole period
  30. ///! RISE: 8 (16) fast PWM cycles with increasing duty up to steady ON
  31. ///! ONE: steady 1 (ON), no change for the whole period
  32. ///! FALL: 8 (16) fast PWM cycles with decreasing duty down to steady OFF
  33. ///! @@TODO move it into progmem
  34. static States stateTable[4*2] = {
  35. // off on
  36. States::ZERO, States::RISE, // ZERO
  37. States::FALL, States::ONE, // RISE
  38. States::FALL, States::ONE, // ONE
  39. States::ZERO, States::RISE // FALL
  40. };
  41. ///! Inner states of the finite automaton
  42. static States state = States::ZERO;
  43. ///! Inner and outer PWM counters
  44. static uint8_t outer = 0;
  45. static uint8_t inner = 0;
  46. static uint8_t pwm = 0;
  47. ///! the slow PWM duty for the next 30Hz cycle
  48. ///! Set in the whole firmware at various places
  49. extern unsigned char soft_pwm_bed;
  50. /// Fine tuning of automaton cycles
  51. #if 1
  52. static const uint8_t innerMax = 16;
  53. static const uint8_t innerShift = 4;
  54. #else
  55. static const uint8_t innerMax = 8;
  56. static const uint8_t innerShift = 5;
  57. #endif
  58. ISR(TIMER0_OVF_vect) // timer compare interrupt service routine
  59. {
  60. if( inner ){
  61. switch(state){
  62. case States::ZERO:
  63. OCR0B = 255;
  64. // Commenting the following code saves 6B, but it is left here for reference
  65. // It is not necessary to set it all over again, because we can only get into the ZERO state from the FALL state (which sets this register)
  66. // TCCR0A |= (1 << COM0B1) | (1 << COM0B0);
  67. break;
  68. case States::RISE:
  69. OCR0B = (innerMax - inner) << innerShift;
  70. // TCCR0A |= (1 << COM0B1); // this bit is always 1
  71. TCCR0A &= ~(1 << COM0B0);
  72. break;
  73. case States::ONE:
  74. OCR0B = 255;
  75. // again - may be skipped, because we get into the ONE state only from RISE (which sets this register)
  76. // TCCR0A |= (1 << COM0B1);
  77. TCCR0A &= ~(1 << COM0B0);
  78. break;
  79. case States::FALL:
  80. OCR0B = (innerMax - inner) << innerShift; // this is the same as in RISE, because now we are setting the zero part of duty due to inverting mode
  81. // 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
  82. TCCR0A |= /*(1 << COM0B1) |*/ (1 << COM0B0);
  83. break;
  84. }
  85. --inner;
  86. } else {
  87. if( ! outer ){ // at the end of 30Hz PWM period
  88. // synchro is not needed (almost), soft_pwm_bed is just 1 byte, 1-byte write instruction is atomic
  89. pwm = soft_pwm_bed << 1;
  90. }
  91. if( pwm > outer || pwm >= 254 ){
  92. // soft_pwm_bed has a range of 0-127, that why a <<1 is done here. That also means that we may get only up to 254 which we want to be full-time 1 (ON)
  93. state = stateTable[ uint8_t(state) * 2 + 1 ];
  94. } else {
  95. // switch OFF
  96. state = stateTable[ uint8_t(state) * 2 + 0 ];
  97. }
  98. ++outer;
  99. inner = innerMax;
  100. }
  101. }