diff --git a/Firmware/Dcodes.cpp b/Firmware/Dcodes.cpp index f943b4a3..1f902c5e 100644 --- a/Firmware/Dcodes.cpp +++ b/Firmware/Dcodes.cpp @@ -635,6 +635,98 @@ void dcode_12() } +#ifdef HEATBED_ANALYSIS + /*! + ### D80 - Bed check D80: Bed check + This command will log data to SD card file "mesh.txt". + #### Usage + + D80 [ E | F | G | H | I | J ] + + #### Parameters + - `E` - Dimension X (default 40) + - `F` - Dimention Y (default 40) + - `G` - Points X (default 40) + - `H` - Points Y (default 40) + - `I` - Offset X (default 74) + - `J` - Offset Y (default 34) + */ +void dcode_80() +{ + float dimension_x = 40; + float dimension_y = 40; + int points_x = 40; + int points_y = 40; + float offset_x = 74; + float offset_y = 33; + + if (code_seen('E')) dimension_x = code_value(); + if (code_seen('F')) dimension_y = code_value(); + if (code_seen('G')) {points_x = code_value(); } + if (code_seen('H')) {points_y = code_value(); } + if (code_seen('I')) {offset_x = code_value(); } + if (code_seen('J')) {offset_y = code_value(); } + printf_P(PSTR("DIM X: %f\n"), dimension_x); + printf_P(PSTR("DIM Y: %f\n"), dimension_y); + printf_P(PSTR("POINTS X: %d\n"), points_x); + printf_P(PSTR("POINTS Y: %d\n"), points_y); + printf_P(PSTR("OFFSET X: %f\n"), offset_x); + printf_P(PSTR("OFFSET Y: %f\n"), offset_y); + bed_check(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); +} + + + /*! + ### D81 - Bed analysis D80: Bed analysis + This command will log data to SD card file "wldsd.txt". + #### Usage + + D81 [ E | F | G | H | I | J ] + + #### Parameters + - `E` - Dimension X (default 40) + - `F` - Dimention Y (default 40) + - `G` - Points X (default 40) + - `H` - Points Y (default 40) + - `I` - Offset X (default 74) + - `J` - Offset Y (default 34) + */ +void dcode_81() +{ + float dimension_x = 40; + float dimension_y = 40; + int points_x = 40; + int points_y = 40; + float offset_x = 74; + float offset_y = 33; + + if (code_seen('E')) dimension_x = code_value(); + if (code_seen('F')) dimension_y = code_value(); + if (code_seen("G")) { strchr_pointer+=1; points_x = code_value(); } + if (code_seen("H")) { strchr_pointer+=1; points_y = code_value(); } + if (code_seen("I")) { strchr_pointer+=1; offset_x = code_value(); } + if (code_seen("J")) { strchr_pointer+=1; offset_y = code_value(); } + + bed_analysis(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); + +} + +#endif //HEATBED_ANALYSIS + + /*! + ### D106 - Print measured fan speed for different pwm values D106: Print measured fan speed for different pwm values + */ +void dcode_106() +{ + for (int i = 255; i > 0; i = i - 5) { + fanSpeed = i; + //delay_keep_alive(2000); + for (int j = 0; j < 100; j++) { + delay_keep_alive(100); + } + printf_P(_N("%d: %d\n"), i, fan_speed[1]); + } +} #ifdef TMC2130 #include "planner.h" diff --git a/Firmware/Dcodes.h b/Firmware/Dcodes.h index eaf849ed..894cba52 100644 --- a/Firmware/Dcodes.h +++ b/Firmware/Dcodes.h @@ -2,26 +2,40 @@ #define DCODES_H extern void dcode__1(); //D-1 - Endless loop (to simulate deadlock) - extern void dcode_0(); //D0 - Reset extern void dcode_1(); //D1 - Clear EEPROM extern void dcode_2(); //D2 - Read/Write RAM + +#ifdef DEBUG_DCODE3 extern void dcode_3(); //D3 - Read/Write EEPROM +#endif //DEBUG_DCODE3 + extern void dcode_4(); //D4 - Read/Write PIN + +#ifdef DEBUG_DCODE5 extern void dcode_5(); //D5 - Read/Write FLASH +#endif //DEBUG_DCODE5 + extern void dcode_6(); //D6 - Read/Write external FLASH extern void dcode_7(); //D7 - Read/Write Bootloader extern void dcode_8(); //D8 - Read/Write PINDA extern void dcode_9(); //D9 - Read/Write ADC (Write=enable simulated, Read=disable simulated) - extern void dcode_10(); //D10 - XYZ calibration = OK +extern void dcode_12(); //D12 - Log time. Writes the current time in the log file. + +#ifdef HEATBED_ANALYSIS +extern void dcode_80(); //D80 - Bed check. This command will log data to SD card file "mesh.txt". +extern void dcode_81(); //D81 - Bed analysis. This command will log data to SD card file "wldsd.txt". +#endif //HEATBED_ANALYSIS + + extern void dcode_106(); //D106 - Print measured fan speed for different pwm values #ifdef TMC2130 -extern void dcode_2130(); //D2130 - TMC2130 + extern void dcode_2130(); //D2130 - TMC2130 #endif //TMC2130 #ifdef PAT9125 -extern void dcode_9125(); //D9125 - PAT9125 + extern void dcode_9125(); //D9125 - PAT9125 #endif //PAT9125 diff --git a/Firmware/Marlin.h b/Firmware/Marlin.h index fb70dfed..363407e2 100755 --- a/Firmware/Marlin.h +++ b/Firmware/Marlin.h @@ -310,9 +310,9 @@ extern int8_t lcd_change_fil_state; extern float default_retraction; #ifdef TMC2130 -bool homeaxis(int axis, bool doError = true, uint8_t cnt = 1, uint8_t* pstep = 0); +void homeaxis(int axis, uint8_t cnt = 1, uint8_t* pstep = 0); #else -bool homeaxis(int axis, bool doError = true, uint8_t cnt = 1); +void homeaxis(int axis, uint8_t cnt = 1); #endif //TMC2130 diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index a6becaa6..8b3d4a73 100755 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -2197,9 +2197,24 @@ bool calibrate_z_auto() #endif //TMC2130 #ifdef TMC2130 -bool homeaxis(int axis, bool doError, uint8_t cnt, uint8_t* pstep) +static void check_Z_crash(void) +{ + if (READ(Z_TMC2130_DIAG) != 0) { //Z crash + FORCE_HIGH_POWER_END; + current_position[Z_AXIS] = 0; + plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + current_position[Z_AXIS] += MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS], active_extruder); + st_synchronize(); + kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); + } +} +#endif //TMC2130 + +#ifdef TMC2130 +void homeaxis(int axis, uint8_t cnt, uint8_t* pstep) #else -bool homeaxis(int axis, bool doError, uint8_t cnt) +void homeaxis(int axis, uint8_t cnt) #endif //TMC2130 { bool endstops_enabled = enable_endstops(true); //RP: endstops should be allways enabled durring homing @@ -2312,13 +2327,7 @@ bool homeaxis(int axis, bool doError, uint8_t cnt) plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); st_synchronize(); #ifdef TMC2130 - if (READ(Z_TMC2130_DIAG) != 0) { //Z crash - FORCE_HIGH_POWER_END; - if (doError) kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); - current_position[axis] = -5; //assume that nozzle crashed into bed - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - return 0; - } + check_Z_crash(); #endif //TMC2130 current_position[axis] = 0; plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); @@ -2330,13 +2339,7 @@ bool homeaxis(int axis, bool doError, uint8_t cnt) plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); st_synchronize(); #ifdef TMC2130 - if (READ(Z_TMC2130_DIAG) != 0) { //Z crash - FORCE_HIGH_POWER_END; - if (doError) kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); - current_position[axis] = -5; //assume that nozzle crashed into bed - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - return 0; - } + check_Z_crash(); #endif //TMC2130 axis_is_at_home(axis); destination[axis] = current_position[axis]; @@ -2348,7 +2351,6 @@ bool homeaxis(int axis, bool doError, uint8_t cnt) #endif } enable_endstops(endstops_enabled); - return 1; } /**/ @@ -8982,28 +8984,7 @@ Sigma_Exit: - `J` - Offset Y (default 34) */ case 80: - { - float dimension_x = 40; - float dimension_y = 40; - int points_x = 40; - int points_y = 40; - float offset_x = 74; - float offset_y = 33; - - if (code_seen('E')) dimension_x = code_value(); - if (code_seen('F')) dimension_y = code_value(); - if (code_seen('G')) {points_x = code_value(); } - if (code_seen('H')) {points_y = code_value(); } - if (code_seen('I')) {offset_x = code_value(); } - if (code_seen('J')) {offset_y = code_value(); } - printf_P(PSTR("DIM X: %f\n"), dimension_x); - printf_P(PSTR("DIM Y: %f\n"), dimension_y); - printf_P(PSTR("POINTS X: %d\n"), points_x); - printf_P(PSTR("POINTS Y: %d\n"), points_y); - printf_P(PSTR("OFFSET X: %f\n"), offset_x); - printf_P(PSTR("OFFSET Y: %f\n"), offset_y); - bed_check(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); - }break; + dcode_80(); break; /*! ### D81 - Bed analysis D80: Bed analysis @@ -9021,24 +9002,7 @@ Sigma_Exit: - `J` - Offset Y (default 34) */ case 81: - { - float dimension_x = 40; - float dimension_y = 40; - int points_x = 40; - int points_y = 40; - float offset_x = 74; - float offset_y = 33; - - if (code_seen('E')) dimension_x = code_value(); - if (code_seen('F')) dimension_y = code_value(); - if (code_seen("G")) { strchr_pointer+=1; points_x = code_value(); } - if (code_seen("H")) { strchr_pointer+=1; points_y = code_value(); } - if (code_seen("I")) { strchr_pointer+=1; offset_x = code_value(); } - if (code_seen("J")) { strchr_pointer+=1; offset_y = code_value(); } - - bed_analysis(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); - - } break; + dcode_81(); break; #endif //HEATBED_ANALYSIS #ifdef DEBUG_DCODES @@ -9047,17 +9011,7 @@ Sigma_Exit: ### D106 - Print measured fan speed for different pwm values D106: Print measured fan speed for different pwm values */ case 106: - { - for (int i = 255; i > 0; i = i - 5) { - fanSpeed = i; - //delay_keep_alive(2000); - for (int j = 0; j < 100; j++) { - delay_keep_alive(100); - - } - printf_P(_N("%d: %d\n"), i, fan_speed[1]); - } - }break; + dcode_106(); break; #ifdef TMC2130 /*! diff --git a/Firmware/heatbed_pwm.cpp b/Firmware/heatbed_pwm.cpp index e8551501..4cd0bf5e 100755 --- a/Firmware/heatbed_pwm.cpp +++ b/Firmware/heatbed_pwm.cpp @@ -1,190 +1,190 @@ -#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) - -// 2019-08-14 update: the original algorithm worked very well, however there were 2 regressions: -// 1. 62kHz ISR requires considerable amount of processing power, -// USB transfer speed dropped by 20%, which was most notable when doing short G-code segments. -// 2. Some users reported TLed PSU started clicking when running at 120V/60Hz. -// This looks like the original algorithm didn't maintain base PWM 30Hz, but only 15Hz -// To address both issues, there is an improved approach based on the idea of leveraging -// different CLK prescalers in some automaton states - i.e. when holding LOW or HIGH on the output pin, -// we don't have to clock 62kHz, but we can increase the CLK prescaler for these states to 8 (or even 64). -// That shall result in the ISR not being called that much resulting in regained performance -// Theoretically this is relatively easy, however one must be very carefull handling the AVR's timer -// control registers correctly, especially setting them in a correct order. -// Some registers are double buffered, some changes are applied in next cycles etc. -// The biggest problem was with the CLK prescaler itself - this circuit is shared among almost all timers, -// we don't want to reset the prescaler counted value when transiting among automaton states. -// Resetting the prescaler would make the PWM more precise, right now there are temporal segments -// of variable period ranging from 0 to 7 62kHz ticks - that's logical, the timer must "sync" -// to the new slower CLK after setting the slower prescaler value. -// In our application, this isn't any significant problem and may be ignored. -// Doing changes in timer's registers non-correctly results in artefacts on the output pin -// - it can toggle unnoticed, which will result in bed clicking again. -// That's why there are special transition states ZERO_TO_RISE and ONE_TO_FALL, which enable the -// counter change its operation atomically and without artefacts on the output pin. -// The resulting signal on the output pin was checked with an osciloscope. -// If there are any change requirements in the future, the signal must be checked with an osciloscope again, -// ad-hoc changes may completely screw things up! - -// 2020-01-29 update: we are introducing a new option to the automaton that will allow us to force the output state -// to either full ON or OFF. This is so that interference during the MBL probing is minimal. -// To accomplish this goal we use bedPWMDisabled. It is only supposed to be used for brief periods of time as to -// not make the bed temperature too unstable. Also, careful consideration should be used when using this -// option as leaving this enabled will also keep the bed output in the state it stopped in. - -///! Definition off finite automaton states -enum class States : uint8_t { - ZERO_START = 0,///< entry point of the automaton - reads the soft_pwm_bed value for the next whole PWM cycle - ZERO, ///< steady 0 (OFF), no change for the whole period - ZERO_TO_RISE, ///< metastate allowing the timer change its state atomically without artefacts on the output pin - RISE, ///< 16 fast PWM cycles with increasing duty up to steady ON - RISE_TO_ONE, ///< metastate allowing the timer change its state atomically without artefacts on the output pin - ONE, ///< steady 1 (ON), no change for the whole period - ONE_TO_FALL, ///< metastate allowing the timer change its state atomically without artefacts on the output pin - FALL, ///< 16 fast PWM cycles with decreasing duty down to steady OFF - FALL_TO_ZERO ///< metastate allowing the timer change its state atomically without artefacts on the output pin -}; - -///! Inner states of the finite automaton -static States state = States::ZERO_START; - -bool bedPWMDisabled = 0; - -///! Fast PWM counter is used in the RISE and FALL states (62.5kHz) -static uint8_t slowCounter = 0; -///! Slow PWM counter is used in the ZERO and ONE states (62.5kHz/8 or 64) -static uint8_t fastCounter = 0; -///! PWM counter for the whole cycle - a cache for soft_pwm_bed -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; - -/// fastMax - how many fast PWM steps to do in RISE and FALL states -/// 16 is a good compromise between silenced bed ("smooth" edges) -/// and not burning the switching MOSFET -static const uint8_t fastMax = 16; - -/// Scaler 16->256 for fast PWM -static const uint8_t fastShift = 4; - -/// Increment slow PWM counter by slowInc every ZERO or ONE state -/// This allows for fine-tuning the basic PWM switching frequency -/// A possible further optimization - use a 64 prescaler (instead of 8) -/// increment slowCounter by 1 -/// but use less bits of soft PWM - something like soft_pwm_bed >> 2 -/// that may further reduce the CPU cycles required by the bed heating automaton -/// 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 -static const uint8_t slowInc = 1; - -ISR(TIMER0_OVF_vect) // timer compare interrupt service routine -{ - switch(state){ - case States::ZERO_START: - if (bedPWMDisabled) return; // stay in the OFF state and do not change the output pin - pwm = soft_pwm_bed << 1;// expecting soft_pwm_bed to be 7bit! - if( pwm != 0 ){ - state = States::ZERO; // do nothing, let it tick once again after the 30Hz period - } - break; - case States::ZERO: // end of state ZERO - we'll either stay in ZERO or change to RISE - // In any case update our cache of pwm value for the next whole cycle from soft_pwm_bed - slowCounter += slowInc; // this does software timer_clk/256 or less (depends on slowInc) - if( slowCounter > pwm ){ - return; - } // otherwise moving towards RISE - state = States::ZERO_TO_RISE; // and finalize the change in a transitional state RISE0 - break; - // even though it may look like the ZERO state may be glued together with the ZERO_TO_RISE, don't do it - // the timer must tick once more in order to get rid of occasional output pin toggles. - case States::ZERO_TO_RISE: // special state for handling transition between prescalers and switching inverted->non-inverted fast-PWM without toggling the output pin. - // It must be done in consequent steps, otherwise the pin will get flipped up and down during one PWM cycle. - // Also beware of the correct sequence of the following timer control registers initialization - it really matters! - state = States::RISE; // prepare for standard RISE cycles - fastCounter = fastMax - 1;// we'll do 16-1 cycles of RISE - TCNT0 = 255; // force overflow on the next clock cycle - TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz - TCCR0A &= ~(1 << COM0B0); // Clear OC0B on Compare Match, set OC0B at BOTTOM (non-inverting mode) - break; - case States::RISE: - OCR0B = (fastMax - fastCounter) << fastShift; - if( fastCounter ){ - --fastCounter; - } else { // end of RISE cycles, changing into state ONE - state = States::RISE_TO_ONE; - OCR0B = 255; // full duty - TCNT0 = 254; // make the timer overflow in the next cycle - // @@TODO these constants are still subject to investigation - } - break; - case States::RISE_TO_ONE: - state = States::ONE; - OCR0B = 255; // full duty - TCNT0 = 255; // make the timer overflow in the next cycle - TCCR0B = (1 << CS01); // change prescaler to 8, i.e. 7.8kHz - break; - case States::ONE: // state ONE - we'll either stay in ONE or change to FALL - OCR0B = 255; - if (bedPWMDisabled) return; // stay in the ON state and do not change the output pin - slowCounter += slowInc; // this does software timer_clk/256 or less - if( slowCounter < pwm ){ - return; - } - if( (soft_pwm_bed << 1) >= (255 - slowInc - 1) ){ //@@TODO simplify & explain - // if slowInc==2, soft_pwm == 251 will be the first to do short drops to zero. 252 will keep full heating - return; // want full duty for the next ONE cycle again - so keep on heating and just wait for the next timer ovf - } - // otherwise moving towards FALL - // @@TODO it looks like ONE_TO_FALL isn't necessary, there are no artefacts at all - state = States::ONE;//_TO_FALL; -// TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz -// break; -// case States::ONE_TO_FALL: -// OCR0B = 255; // zero duty - state=States::FALL; - fastCounter = fastMax - 1;// we'll do 16-1 cycles of RISE - TCNT0 = 255; // force overflow on the next clock cycle - TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz - // 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 - // COM0B1 remains set both in inverting and non-inverting mode - TCCR0A |= (1 << COM0B0); // inverting mode - break; - case States::FALL: - 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 - //TCCR0A |= (1 << COM0B0); // already set in ONE_TO_FALL - if( fastCounter ){ - --fastCounter; - } else { // end of FALL cycles, changing into state ZERO - state = States::FALL_TO_ZERO; - TCNT0 = 128; //@@TODO again - need to wait long enough to propagate the timer state changes - OCR0B = 255; - } - break; - case States::FALL_TO_ZERO: - state = States::ZERO_START; // go to read new soft_pwm_bed value for the next cycle - TCNT0 = 128; - OCR0B = 255; - TCCR0B = (1 << CS01); // change prescaler to 8, i.e. 7.8kHz - break; - } -} +#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) + +// 2019-08-14 update: the original algorithm worked very well, however there were 2 regressions: +// 1. 62kHz ISR requires considerable amount of processing power, +// USB transfer speed dropped by 20%, which was most notable when doing short G-code segments. +// 2. Some users reported TLed PSU started clicking when running at 120V/60Hz. +// This looks like the original algorithm didn't maintain base PWM 30Hz, but only 15Hz +// To address both issues, there is an improved approach based on the idea of leveraging +// different CLK prescalers in some automaton states - i.e. when holding LOW or HIGH on the output pin, +// we don't have to clock 62kHz, but we can increase the CLK prescaler for these states to 8 (or even 64). +// That shall result in the ISR not being called that much resulting in regained performance +// Theoretically this is relatively easy, however one must be very carefull handling the AVR's timer +// control registers correctly, especially setting them in a correct order. +// Some registers are double buffered, some changes are applied in next cycles etc. +// The biggest problem was with the CLK prescaler itself - this circuit is shared among almost all timers, +// we don't want to reset the prescaler counted value when transiting among automaton states. +// Resetting the prescaler would make the PWM more precise, right now there are temporal segments +// of variable period ranging from 0 to 7 62kHz ticks - that's logical, the timer must "sync" +// to the new slower CLK after setting the slower prescaler value. +// In our application, this isn't any significant problem and may be ignored. +// Doing changes in timer's registers non-correctly results in artefacts on the output pin +// - it can toggle unnoticed, which will result in bed clicking again. +// That's why there are special transition states ZERO_TO_RISE and ONE_TO_FALL, which enable the +// counter change its operation atomically and without artefacts on the output pin. +// The resulting signal on the output pin was checked with an osciloscope. +// If there are any change requirements in the future, the signal must be checked with an osciloscope again, +// ad-hoc changes may completely screw things up! + +// 2020-01-29 update: we are introducing a new option to the automaton that will allow us to force the output state +// to either full ON or OFF. This is so that interference during the MBL probing is minimal. +// To accomplish this goal we use bedPWMDisabled. It is only supposed to be used for brief periods of time as to +// not make the bed temperature too unstable. Also, careful consideration should be used when using this +// option as leaving this enabled will also keep the bed output in the state it stopped in. + +///! Definition off finite automaton states +enum class States : uint8_t { + ZERO_START = 0,///< entry point of the automaton - reads the soft_pwm_bed value for the next whole PWM cycle + ZERO, ///< steady 0 (OFF), no change for the whole period + ZERO_TO_RISE, ///< metastate allowing the timer change its state atomically without artefacts on the output pin + RISE, ///< 16 fast PWM cycles with increasing duty up to steady ON + RISE_TO_ONE, ///< metastate allowing the timer change its state atomically without artefacts on the output pin + ONE, ///< steady 1 (ON), no change for the whole period + ONE_TO_FALL, ///< metastate allowing the timer change its state atomically without artefacts on the output pin + FALL, ///< 16 fast PWM cycles with decreasing duty down to steady OFF + FALL_TO_ZERO ///< metastate allowing the timer change its state atomically without artefacts on the output pin +}; + +///! Inner states of the finite automaton +static States state = States::ZERO_START; + +bool bedPWMDisabled = 0; + +///! Fast PWM counter is used in the RISE and FALL states (62.5kHz) +static uint8_t slowCounter = 0; +///! Slow PWM counter is used in the ZERO and ONE states (62.5kHz/8 or 64) +static uint8_t fastCounter = 0; +///! PWM counter for the whole cycle - a cache for soft_pwm_bed +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; + +/// fastMax - how many fast PWM steps to do in RISE and FALL states +/// 16 is a good compromise between silenced bed ("smooth" edges) +/// and not burning the switching MOSFET +static const uint8_t fastMax = 16; + +/// Scaler 16->256 for fast PWM +static const uint8_t fastShift = 4; + +/// Increment slow PWM counter by slowInc every ZERO or ONE state +/// This allows for fine-tuning the basic PWM switching frequency +/// A possible further optimization - use a 64 prescaler (instead of 8) +/// increment slowCounter by 1 +/// but use less bits of soft PWM - something like soft_pwm_bed >> 2 +/// that may further reduce the CPU cycles required by the bed heating automaton +/// 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 +static const uint8_t slowInc = 1; + +ISR(TIMER0_OVF_vect) // timer compare interrupt service routine +{ + switch(state){ + case States::ZERO_START: + if (bedPWMDisabled) return; // stay in the OFF state and do not change the output pin + pwm = soft_pwm_bed << 1;// expecting soft_pwm_bed to be 7bit! + if( pwm != 0 ){ + state = States::ZERO; // do nothing, let it tick once again after the 30Hz period + } + break; + case States::ZERO: // end of state ZERO - we'll either stay in ZERO or change to RISE + // In any case update our cache of pwm value for the next whole cycle from soft_pwm_bed + slowCounter += slowInc; // this does software timer_clk/256 or less (depends on slowInc) + if( slowCounter > pwm ){ + return; + } // otherwise moving towards RISE + state = States::ZERO_TO_RISE; // and finalize the change in a transitional state RISE0 + break; + // even though it may look like the ZERO state may be glued together with the ZERO_TO_RISE, don't do it + // the timer must tick once more in order to get rid of occasional output pin toggles. + case States::ZERO_TO_RISE: // special state for handling transition between prescalers and switching inverted->non-inverted fast-PWM without toggling the output pin. + // It must be done in consequent steps, otherwise the pin will get flipped up and down during one PWM cycle. + // Also beware of the correct sequence of the following timer control registers initialization - it really matters! + state = States::RISE; // prepare for standard RISE cycles + fastCounter = fastMax - 1;// we'll do 16-1 cycles of RISE + TCNT0 = 255; // force overflow on the next clock cycle + TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz + TCCR0A &= ~(1 << COM0B0); // Clear OC0B on Compare Match, set OC0B at BOTTOM (non-inverting mode) + break; + case States::RISE: + OCR0B = (fastMax - fastCounter) << fastShift; + if( fastCounter ){ + --fastCounter; + } else { // end of RISE cycles, changing into state ONE + state = States::RISE_TO_ONE; + OCR0B = 255; // full duty + TCNT0 = 254; // make the timer overflow in the next cycle + // @@TODO these constants are still subject to investigation + } + break; + case States::RISE_TO_ONE: + state = States::ONE; + OCR0B = 255; // full duty + TCNT0 = 255; // make the timer overflow in the next cycle + TCCR0B = (1 << CS01); // change prescaler to 8, i.e. 7.8kHz + break; + case States::ONE: // state ONE - we'll either stay in ONE or change to FALL + OCR0B = 255; + if (bedPWMDisabled) return; // stay in the ON state and do not change the output pin + slowCounter += slowInc; // this does software timer_clk/256 or less + if( slowCounter < pwm ){ + return; + } + if( (soft_pwm_bed << 1) >= (255 - slowInc - 1) ){ //@@TODO simplify & explain + // if slowInc==2, soft_pwm == 251 will be the first to do short drops to zero. 252 will keep full heating + return; // want full duty for the next ONE cycle again - so keep on heating and just wait for the next timer ovf + } + // otherwise moving towards FALL + // @@TODO it looks like ONE_TO_FALL isn't necessary, there are no artefacts at all + state = States::ONE;//_TO_FALL; +// TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz +// break; +// case States::ONE_TO_FALL: +// OCR0B = 255; // zero duty + state=States::FALL; + fastCounter = fastMax - 1;// we'll do 16-1 cycles of RISE + TCNT0 = 255; // force overflow on the next clock cycle + TCCR0B = (1 << CS00); // change prescaler to 1, i.e. 62.5kHz + // 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 + // COM0B1 remains set both in inverting and non-inverting mode + TCCR0A |= (1 << COM0B0); // inverting mode + break; + case States::FALL: + 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 + //TCCR0A |= (1 << COM0B0); // already set in ONE_TO_FALL + if( fastCounter ){ + --fastCounter; + } else { // end of FALL cycles, changing into state ZERO + state = States::FALL_TO_ZERO; + TCNT0 = 128; //@@TODO again - need to wait long enough to propagate the timer state changes + OCR0B = 255; + } + break; + case States::FALL_TO_ZERO: + state = States::ZERO_START; // go to read new soft_pwm_bed value for the next cycle + TCNT0 = 128; + OCR0B = 255; + TCCR0B = (1 << CS01); // change prescaler to 8, i.e. 7.8kHz + break; + } +} diff --git a/Firmware/tmc2130.cpp b/Firmware/tmc2130.cpp index 3a4e5b51..108d00b1 100755 --- a/Firmware/tmc2130.cpp +++ b/Firmware/tmc2130.cpp @@ -994,7 +994,7 @@ bool tmc2130_home_calibrate(uint8_t axis) uint8_t step[16]; uint8_t cnt[16]; uint8_t val[16]; - homeaxis(axis, true, 16, step); + homeaxis(axis, 16, step); bubblesort_uint8(step, 16, 0); printf_P(PSTR("sorted samples:\n")); for (uint8_t i = 0; i < 16; i++) diff --git a/Firmware/ultralcd.cpp b/Firmware/ultralcd.cpp index daf9f017..62c41072 100755 --- a/Firmware/ultralcd.cpp +++ b/Firmware/ultralcd.cpp @@ -168,10 +168,10 @@ static void reset_crash_det(unsigned char axis); static bool lcd_selfcheck_axis_sg(unsigned char axis); static bool lcd_selfcheck_axis(int _axis, int _travel); #else -static bool lcd_selfcheck_endstops(); static bool lcd_selfcheck_axis(int _axis, int _travel); static bool lcd_selfcheck_pulleys(int axis); #endif //TMC2130 +static bool lcd_selfcheck_endstops(); static bool lcd_selfcheck_check_heater(bool _isbed); enum class TestScreen : uint_least8_t @@ -7667,11 +7667,7 @@ bool lcd_selftest() if (_result) { _progress = lcd_selftest_screen(TestScreen::FansOk, _progress, 3, true, 2000); -#ifndef TMC2130 - _result = lcd_selfcheck_endstops(); -#else - _result = true; -#endif + _result = lcd_selfcheck_endstops(); //With TMC2130, only the Z probe is tested. } if (_result) @@ -7738,7 +7734,7 @@ bool lcd_selftest() set_destination_to_current(); _progress = lcd_selftest_screen(TestScreen::AxisZ, _progress, 3, true, 1500); #ifdef TMC2130 - _result = homeaxis(Z_AXIS, false); + homeaxis(Z_AXIS); //In case of failure, the code gets stuck in this function. #else _result = lcd_selfcheck_axis(Z_AXIS, Z_MAX_POS); #endif //TMC2130 @@ -8137,31 +8133,42 @@ static bool lcd_selfcheck_pulleys(int axis) } return(true); } +#endif //not defined TMC2130 static bool lcd_selfcheck_endstops() { bool _result = true; - if (((READ(X_MIN_PIN) ^ X_MIN_ENDSTOP_INVERTING) == 1) || + if ( + #ifndef TMC2130 + ((READ(X_MIN_PIN) ^ X_MIN_ENDSTOP_INVERTING) == 1) || ((READ(Y_MIN_PIN) ^ Y_MIN_ENDSTOP_INVERTING) == 1) || + #endif //!TMC2130 ((READ(Z_MIN_PIN) ^ Z_MIN_ENDSTOP_INVERTING) == 1)) { + #ifndef TMC2130 if ((READ(X_MIN_PIN) ^ X_MIN_ENDSTOP_INVERTING) == 1) current_position[0] += 10; if ((READ(Y_MIN_PIN) ^ Y_MIN_ENDSTOP_INVERTING) == 1) current_position[1] += 10; + #endif //!TMC2130 if ((READ(Z_MIN_PIN) ^ Z_MIN_ENDSTOP_INVERTING) == 1) current_position[2] += 10; } plan_buffer_line_curposXYZE(manual_feedrate[0] / 60, active_extruder); - _delay(500); + st_synchronize(); - if (((READ(X_MIN_PIN) ^ X_MIN_ENDSTOP_INVERTING) == 1) || + if ( + #ifndef TMC2130 + ((READ(X_MIN_PIN) ^ X_MIN_ENDSTOP_INVERTING) == 1) || ((READ(Y_MIN_PIN) ^ Y_MIN_ENDSTOP_INVERTING) == 1) || + #endif //!TMC2130 ((READ(Z_MIN_PIN) ^ Z_MIN_ENDSTOP_INVERTING) == 1)) { _result = false; char _error[4] = ""; + #ifndef TMC2130 if ((READ(X_MIN_PIN) ^ X_MIN_ENDSTOP_INVERTING) == 1) strcat(_error, "X"); if ((READ(Y_MIN_PIN) ^ Y_MIN_ENDSTOP_INVERTING) == 1) strcat(_error, "Y"); + #endif //!TMC2130 if ((READ(Z_MIN_PIN) ^ Z_MIN_ENDSTOP_INVERTING) == 1) strcat(_error, "Z"); lcd_selftest_error(TestError::Endstops, _error, ""); } @@ -8169,7 +8176,6 @@ static bool lcd_selfcheck_endstops() manage_inactivity(true); return _result; } -#endif //not defined TMC2130 static bool lcd_selfcheck_check_heater(bool _isbed) { diff --git a/README.md b/README.md index 54070dce..9d62d9ae 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ - For MK3 --> skip to step 3. - If you have a different printer model, follow step [2.b](#2b) from Windows build -3. Run `sudo ./build.sh` +3. Run `./build.sh` - Output hex file is at `"PrusaFirmware/lang/firmware.hex"` . In the same folder you can hex files for other languages as well. 4. Connect your printer and flash with PrusaSlicer ( Configuration --> Flash printer firmware ) or Slic3r PE. diff --git a/lang/lang_en_fr.txt b/lang/lang_en_fr.txt index f6add948..51ddf159 100755 --- a/lang/lang_en_fr.txt +++ b/lang/lang_en_fr.txt @@ -20,7 +20,7 @@ #MSG_CRASH_DET_STEALTH_FORCE_OFF c=20 r=4 "WARNING:\x0aCrash detection\x0adisabled in\x0aStealth mode" -"ATTENTION:\x0aDetection de crash\x0adesactivee en\x0amode feutre" +"ATTENTION:\x0aDetection de crash\x0adesactivee en\x0amode furtif" # ">Cancel" @@ -550,7 +550,7 @@ #MSG_SILENT "Silent" -"Feutre" +"Furtif" # "MMU needs user attention."