From a16de8353590d8e4c5125c73f717b03019a664b4 Mon Sep 17 00:00:00 2001 From: DRracer Date: Mon, 8 Jul 2019 16:42:21 +0200 Subject: [PATCH] heatbed audible noise suppression using short fast PWM pulses with variable duty --- Firmware/heatbed_pwm.cpp | 109 +++++++++++++++++++++++++++++++++++++++ Firmware/system_timer.h | 9 ++-- Firmware/temperature.cpp | 53 ++++++++++--------- Firmware/timer02.c | 56 ++++++-------------- Firmware/timer02.h | 15 +++--- 5 files changed, 167 insertions(+), 75 deletions(-) create mode 100755 Firmware/heatbed_pwm.cpp diff --git a/Firmware/heatbed_pwm.cpp b/Firmware/heatbed_pwm.cpp new file mode 100755 index 000000000..8a913b638 --- /dev/null +++ b/Firmware/heatbed_pwm.cpp @@ -0,0 +1,109 @@ +#include +#include +#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; + } +} diff --git a/Firmware/system_timer.h b/Firmware/system_timer.h index d4fbfc39b..ca8f2f9ab 100644 --- a/Firmware/system_timer.h +++ b/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 diff --git a/Firmware/temperature.cpp b/Firmware/temperature.cpp index db0b8628a..688761135 100755 --- a/Firmware/temperature.cpp +++ b/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< -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); diff --git a/Firmware/timer02.c b/Firmware/timer02.c index e0a0a5a67..6b6cbe2cf 100644 --- a/Firmware/timer02.c +++ b/Firmware/timer02.c @@ -9,48 +9,27 @@ #include #include -#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<> 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; diff --git a/Firmware/timer02.h b/Firmware/timer02.h index 103360b9c..48a4842fe 100644 --- a/Firmware/timer02.h +++ b/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)