Browse Source

heatbed audible noise suppression using short fast PWM pulses with
variable duty

DRracer 4 years ago
parent
commit
a16de83535
5 changed files with 167 additions and 75 deletions
  1. 109 0
      Firmware/heatbed_pwm.cpp
  2. 6 3
      Firmware/system_timer.h
  3. 29 24
      Firmware/temperature.cpp
  4. 15 41
      Firmware/timer02.c
  5. 8 7
      Firmware/timer02.h

+ 109 - 0
Firmware/heatbed_pwm.cpp

@@ -0,0 +1,109 @@
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include "io_atmega2560.h"
+
+// All this is about silencing the heat bed, as it behaves like a loudspeaker.
+// Basically, we want the PWM heating switched at 30Hz (or so) which is a well ballanced
+// frequency for both power supply units (i.e. both PSUs are reasonably silent).
+// The only trouble is the rising or falling edge of bed heating - that creates an audible click.
+// This audible click may be suppressed by making the rising or falling edge NOT sharp.
+// Of course, making non-sharp edges in digital technology is not easy, but there is a solution.
+// It is possible to do a fast PWM sequence with duty starting from 0 to 255.
+// Doing this at higher frequency than the bed "loudspeaker" can handle makes the click barely audible.
+// Technically:
+// timer0 is set to fast PWM mode at 62.5kHz (timer0 is linked to the bed heating pin) (zero prescaler)
+// To keep the bed switching at 30Hz - we don't want the PWM running at 62kHz all the time 
+// since it would burn the heatbed's MOSFET:
+// 16MHz/256 levels of PWM duty gives us 62.5kHz
+// 62.5kHz/256 gives ~244Hz, that is still too fast - 244/8 gives ~30Hz, that's what we need
+// So the automaton runs atop of inner 8 (or 16) cycles.
+// The finite automaton is running in the ISR(TIMER0_OVF_vect)
+
+///! Definition off finite automaton states
+enum class States : uint8_t {
+  ZERO = 0,
+  RISE = 1,
+  ONE = 2,
+  FALL = 3
+};
+
+///! State table for the inner part of the finite automaton
+///! Basically it specifies what shall happen if the outer automaton is requesting setting the heat pin to 0 (OFF) or 1 (ON)
+///! ZERO: steady 0 (OFF), no change for the whole period
+///! RISE: 8 (16) fast PWM cycles with increasing duty up to steady ON
+///! ONE:  steady 1 (ON), no change for the whole period 
+///! FALL: 8 (16) fast PWM cycles with decreasing duty down to steady OFF
+///! @@TODO move it into progmem
+static States stateTable[4*2] = {
+// off             on
+States::ZERO,      States::RISE, // ZERO
+States::FALL,      States::ONE,  // RISE
+States::FALL,      States::ONE,  // ONE
+States::ZERO,      States::RISE  // FALL
+};
+
+///! Inner states of the finite automaton
+static States state = States::ZERO;
+
+///! Inner and outer PWM counters
+static uint8_t outer = 0;
+static uint8_t inner = 0;
+static uint8_t pwm = 0;
+
+///! the slow PWM duty for the next 30Hz cycle
+///! Set in the whole firmware at various places
+extern unsigned char soft_pwm_bed;
+
+/// Fine tuning of automaton cycles
+#if 1
+static const uint8_t innerMax = 16;
+static const uint8_t innerShift = 4;
+#else
+static const uint8_t innerMax = 8;
+static const uint8_t innerShift = 5;
+#endif
+
+ISR(TIMER0_OVF_vect)          // timer compare interrupt service routine
+{
+  if( inner ){
+    switch(state){
+    case States::ZERO:
+      OCR0B = 255;
+	  // Commenting the following code saves 6B, but it is left here for reference
+	  // 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)
+//       TCCR0A |= (1 << COM0B1) | (1 << COM0B0);
+      break;
+    case States::RISE:
+      OCR0B = (innerMax - inner) << innerShift;
+//       TCCR0A |= (1 << COM0B1); // this bit is always 1
+      TCCR0A &= ~(1 << COM0B0);
+      break;  
+    case States::ONE:
+      OCR0B = 255;
+	  // again - may be skipped, because we get into the ONE state only from RISE (which sets this register)
+//       TCCR0A |= (1 << COM0B1);
+       TCCR0A &= ~(1 << COM0B0);
+      break;
+    case States::FALL:
+      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
+      // 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
+	  TCCR0A |= /*(1 << COM0B1) |*/ (1 << COM0B0); 
+      break;
+    }
+    --inner;
+  } else {
+    if( ! outer ){ // at the end of 30Hz PWM period
+      // synchro is not needed (almost), soft_pwm_bed is just 1 byte, 1-byte write instruction is atomic
+      pwm = soft_pwm_bed << 1;
+    }
+	if( pwm > outer || pwm >= 254 ){
+      // 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)
+      state = stateTable[ uint8_t(state) * 2 + 1 ];
+    } else {
+      // switch OFF
+      state = stateTable[ uint8_t(state) * 2 + 0 ];
+    }
+    ++outer;
+    inner = innerMax;
+  }
+}

+ 6 - 3
Firmware/system_timer.h

@@ -4,7 +4,7 @@
 #define FIRMWARE_SYSTEM_TIMER_H_
 
 #include "Arduino.h"
-//#define SYSTEM_TIMER_2
+#define SYSTEM_TIMER_2
 
 #ifdef SYSTEM_TIMER_2
 #include "timer02.h"
@@ -13,12 +13,15 @@
 #define _delay delay2
 #define _tone tone2
 #define _noTone noTone2
+
+#define timer02_set_pwm0(pwm0)
+
 #else //SYSTEM_TIMER_2
 #define _millis millis
 #define _micros micros
 #define _delay delay
-#define _tone tone
-#define _noTone noTone
+#define _tone(x, y) /*tone*/
+#define _noTone(x) /*noTone*/
 #define timer02_set_pwm0(pwm0)
 #endif //SYSTEM_TIMER_2
 

+ 29 - 24
Firmware/temperature.cpp

@@ -44,8 +44,6 @@
 #include "Timer.h"
 #include "Configuration_prusa.h"
 
-
-
 //===========================================================================
 //=============================public variables============================
 //===========================================================================
@@ -1130,18 +1128,9 @@ void tp_init()
 
   adc_init();
 
-#ifdef SYSTEM_TIMER_2
-  timer02_init();
+  timer0_init();
   OCR2B = 128;
   TIMSK2 |= (1<<OCIE2B);  
-#else //SYSTEM_TIMER_2
-  // Use timer0 for temperature measurement
-  // Interleave temperature interrupt with millies interrupt
-  OCR0B = 128;
-  TIMSK0 |= (1<<OCIE0B);  
-#endif //SYSTEM_TIMER_2
-
-
   
   // Wait for temperature measurement to settle
   _delay(250);
@@ -1472,8 +1461,8 @@ void disable_heater()
     target_temperature_bed=0;
     soft_pwm_bed=0;
 	timer02_set_pwm0(soft_pwm_bed << 1);
-    #if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1  
-      WRITE(HEATER_BED_PIN,LOW);
+    #if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1
+      //WRITE(HEATER_BED_PIN,LOW);
     #endif
   #endif 
 }
@@ -1544,7 +1533,7 @@ void min_temp_error(uint8_t e) {
 
 void bed_max_temp_error(void) {
 #if HEATER_BED_PIN > -1
-  WRITE(HEATER_BED_PIN, 0);
+  //WRITE(HEATER_BED_PIN, 0);
 #endif
   if(IsStopped() == false) {
     SERIAL_ERROR_START;
@@ -1563,7 +1552,7 @@ void bed_min_temp_error(void) {
 #endif
 //if (current_temperature_ambient < MINTEMP_MINAMBIENT) return;
 #if HEATER_BED_PIN > -1
-    WRITE(HEATER_BED_PIN, 0);
+    //WRITE(HEATER_BED_PIN, 0);
 #endif
 	static const char err[] PROGMEM = "Err: MINTEMP BED";
     if(IsStopped() == false) {
@@ -1660,7 +1649,6 @@ void adc_ready(void) //callback from adc when sampling finished
 
 } // extern "C"
 
-
 // Timer2 (originaly timer0) is shared with millies
 #ifdef SYSTEM_TIMER_2
 ISR(TIMER2_COMPB_vect)
@@ -1676,8 +1664,8 @@ ISR(TIMER0_COMPB_vect)
 	if (!temp_meas_ready) adc_cycle();
 	lcd_buttons_update();
 
-  static unsigned char pwm_count = (1 << SOFT_PWM_SCALE);
-  static unsigned char soft_pwm_0;
+  static uint8_t pwm_count = (1 << SOFT_PWM_SCALE);
+  static uint8_t soft_pwm_0;
 #ifdef SLOW_PWM_HEATERS
   static unsigned char slow_pwm_count = 0;
   static unsigned char state_heater_0 = 0;
@@ -1698,7 +1686,7 @@ ISR(TIMER0_COMPB_vect)
 #endif 
 #endif
 #if HEATER_BED_PIN > -1
-  static unsigned char soft_pwm_b;
+  // @@DR static unsigned char soft_pwm_b;
 #ifdef SLOW_PWM_HEATERS
   static unsigned char state_heater_b = 0;
   static unsigned char state_timer_heater_b = 0;
@@ -1733,14 +1721,25 @@ ISR(TIMER0_COMPB_vect)
 #endif
   }
 #if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1
+  
+#if 0  // @@DR vypnuto pro hw pwm bedu
+  // tuhle prasarnu bude potreba poustet ve stanovenych intervalech, jinak nemam moc sanci zareagovat
+  // teoreticky by se tato cast uz vubec nemusela poustet
   if ((pwm_count & ((1 << HEATER_BED_SOFT_PWM_BITS) - 1)) == 0)
   {
     soft_pwm_b = soft_pwm_bed >> (7 - HEATER_BED_SOFT_PWM_BITS);
-#ifndef SYSTEM_TIMER_2
-	if(soft_pwm_b > 0) WRITE(HEATER_BED_PIN,1); else WRITE(HEATER_BED_PIN,0);
-#endif //SYSTEM_TIMER_2
+#  ifndef SYSTEM_TIMER_2
+	// tady budu krokovat pomalou frekvenci na automatu - tohle je rizeni spinani a rozepinani
+	// jako ridici frekvenci mam 2khz, jako vystupni frekvenci mam 30hz
+	// 2kHz jsou ovsem ve slysitelnem pasmu, mozna bude potreba jit s frekvenci nahoru (a tomu taky prizpusobit ostatni veci)
+	// Teoreticky bych mohl stahnout OCR0B citac na 6, cimz bych se dostal nekam ke 40khz a tady potom honit PWM rychleji nebo i pomaleji
+	// to nicemu nevadi. Soft PWM scale by se 20x zvetsilo (no dobre, 16x), cimz by se to posunulo k puvodnimu 30Hz PWM
+	//if(soft_pwm_b > 0) WRITE(HEATER_BED_PIN,1); else WRITE(HEATER_BED_PIN,0);
+#  endif //SYSTEM_TIMER_2
   }
 #endif
+#endif
+  
 #ifdef FAN_SOFT_PWM
   if ((pwm_count & ((1 << FAN_SOFT_PWM_BITS) - 1)) == 0)
   {
@@ -1762,8 +1761,14 @@ ISR(TIMER0_COMPB_vect)
 #if EXTRUDERS > 2
   if(soft_pwm_2 < pwm_count) WRITE(HEATER_2_PIN,0);
 #endif
+
+#if 0 // @@DR  
 #if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1
-  if (soft_pwm_b < (pwm_count & ((1 << HEATER_BED_SOFT_PWM_BITS) - 1))) WRITE(HEATER_BED_PIN,0);
+  if (soft_pwm_b < (pwm_count & ((1 << HEATER_BED_SOFT_PWM_BITS) - 1))){
+	  //WRITE(HEATER_BED_PIN,0);
+  }
+  //WRITE(HEATER_BED_PIN, pwm_count & 1 );
+#endif
 #endif
 #ifdef FAN_SOFT_PWM
   if (soft_pwm_fan < (pwm_count & ((1 << FAN_SOFT_PWM_BITS) - 1))) WRITE(FAN_PIN,0);

+ 15 - 41
Firmware/timer02.c

@@ -9,48 +9,27 @@
 
 #include <avr/io.h>
 #include <avr/interrupt.h>
-#include "Arduino.h"
 #include "io_atmega2560.h"
 
 #define BEEPER              84
 
-uint8_t timer02_pwm0 = 0;
-
-void timer02_set_pwm0(uint8_t pwm0)
-{
-	if (timer02_pwm0 == pwm0) return;
-	if (pwm0)
-	{
-		TCCR0A |= (2 << COM0B0);
-		OCR0B = pwm0 - 1;
-	}
-	else
-	{
-		TCCR0A &= ~(2 << COM0B0);
-		OCR0B = 0;
-	}
-	timer02_pwm0 = pwm0;
-}
-
-void timer02_init(void)
+void timer0_init(void)
 {
 	//save sreg
 	uint8_t _sreg = SREG;
 	//disable interrupts for sure
 	cli();
-	//mask timer0 interrupts - disable all
-	TIMSK0 &= ~(1<<TOIE0);
-	TIMSK0 &= ~(1<<OCIE0A);
-	TIMSK0 &= ~(1<<OCIE0B);
-	//setup timer0
-	TCCR0A = 0x00; //COM_A-B=00, WGM_0-1=00
-	TCCR0B = (1 << CS00); //WGM_2=0, CS_0-2=011
-	//switch timer0 to fast pwm mode
-	TCCR0A |= (3 << WGM00); //WGM_0-1=11
-	//set OCR0B register to zero
-	OCR0B = 0;
-	//disable OCR0B output (will be enabled in timer02_set_pwm0)
-	TCCR0A &= ~(2 << COM0B0);
+
+	TCNT0  = 0;
+	// Fast PWM duty (0-255). 
+	// Due to invert mode (following rows) the duty is set to 255, which means zero all the time (bed not heating)
+	OCR0B = 255;
+	// Set fast PWM mode and inverting mode.
+	TCCR0A = (1 << WGM01) | (1 << WGM00) | (1 << COM0B1) | (1 << COM0B0);  
+	TCCR0B = (1 << CS00);    // no clock prescaling
+	TIMSK0 |= (1 << TOIE0);  // enable timer overflow interrupt
+	
+	// Everything, that used to be on timer0 was moved to timer2 (delay, beeping, millis etc.)
 	//setup timer2
 	TCCR2A = 0x00; //COM_A-B=00, WGM_0-1=00
 	TCCR2B = (4 << CS20); //WGM_2=0, CS_0-2=011
@@ -66,11 +45,9 @@ void timer02_init(void)
 }
 
 
-//following code is OVF handler for timer 2
-//it is copy-paste from wiring.c and modified for timer2
-//variables timer0_overflow_count and timer0_millis are declared in wiring.c
-
-
+// The following code is OVF handler for timer 2
+// it was copy-pasted from wiring.c and modified for timer2
+// variables timer0_overflow_count and timer0_millis are declared in wiring.c
 
 // the prescaler is set so that timer0 ticks every 64 clock cycles, and the
 // the overflow handler is called every 256 ticks.
@@ -85,9 +62,6 @@ void timer02_init(void)
 #define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
 #define FRACT_MAX (1000 >> 3)
 
-//extern volatile unsigned long timer0_overflow_count;
-//extern volatile unsigned long timer0_millis;
-//unsigned char timer0_fract = 0;
 volatile unsigned long timer2_overflow_count;
 volatile unsigned long timer2_millis;
 unsigned char timer2_fract = 0;

+ 8 - 7
Firmware/timer02.h

@@ -11,24 +11,25 @@
 extern "C" {
 #endif //defined(__cplusplus)
 
+///! Initializes TIMER0 for fast PWM mode-driven bed heating
+extern void timer0_init(void);
 
-extern uint8_t timer02_pwm0;
-
-extern void timer02_set_pwm0(uint8_t pwm0);
-
-extern void timer02_init(void);
-
+///! Reimplemented original millis() using timer2
 extern unsigned long millis2(void);
 
+///! Reimplemented original micros() using timer2
 extern unsigned long micros2(void);
 
+///! Reimplemented original delay() using timer2
 extern void delay2(unsigned long ms);
 
+///! Reimplemented original tone() using timer2
+///! Does not perform any PWM tone generation, it just sets the beeper pin to 1
 extern void tone2(uint8_t _pin, unsigned int frequency/*, unsigned long duration*/);
 
+///! Turn off beeping - set beeper pin to 0
 extern void noTone2(uint8_t _pin);
 
-
 #if defined(__cplusplus)
 }
 #endif //defined(__cplusplus)