heatbed audible noise suppression using short fast PWM pulses with
variable duty
This commit is contained in:
parent
ce128e012f
commit
a16de83535
5 changed files with 167 additions and 75 deletions
109
Firmware/heatbed_pwm.cpp
Executable file
109
Firmware/heatbed_pwm.cpp
Executable file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue