/** * Marlin 3D Printer Firmware * Copyright (C) 2018 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "../../../inc/MarlinConfig.h" #if HAS_DRIVER(L6470) #include "../../gcode.h" #include "../../../module/stepper_indirection.h" #include "../../../module/planner.h" #include "../../../libs/L6470/L6470_Marlin.h" /** * * M916: increase KVAL_HOLD until get thermal warning * * * J - select which driver(s) to monitor on multi-driver axis * 0 - (default) monitor all drivers on the axis or E0 * 1 - monitor only X, Y, Z, E1 * 2 - monitor only X2, Y2, Z2, E2 * 3 - monitor only Z3, E3 * * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement * xxx (1-255) is distance moved on either side of current position * * F - feedrate * optional - will use default max feedrate from configuration.h if not specified * * K - starting value for KVAL_HOLD (0 - 255) * optional - will use & report current value from driver if not specified * */ /** * This routine is also useful for determining the approximate KVAL_HOLD * where the stepper stops losing steps. The sound will get noticeably quieter * as it stops losing steps. */ void GcodeSuite::M916() { L6470_ECHOLNPGM("M916"); // Variables used by L6470_get_user_input function - some may not be used char axis_mon[3][3] = { " ", " ", " " }; // list of Axes to be monitored uint8_t axis_index[3]; uint16_t axis_status[3]; uint8_t driver_count = 1; float position_max; float position_min; float final_feedrate; uint8_t kval_hold; uint8_t ocd_th_val = 0; uint8_t stall_th_val = 0; uint16_t over_current_threshold; constexpr bool over_current_flag = false; // M916 doesn't play with the overcurrent thresholds uint8_t j; // general purpose counter if (L6470.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, ocd_th_val, stall_th_val, over_current_threshold)) return; // quit if invalid user input L6470_ECHOLNPAIR("feedrate = ", final_feedrate); planner.synchronize(); // wait for all current movement commands to complete for (j = 0; j < driver_count; j++) L6470.get_status(axis_index[j]); // clear out any pre-existing error flags char temp_axis_string[] = " "; temp_axis_string[0] = axis_mon[0][0]; // need to have a string for use within sprintf format section char gcode_string[80]; uint16_t status_composite = 0; L6470_ECHOLNPGM(".\n."); do { L6470_ECHOLNPAIR("kval_hold = ", kval_hold); // set & report KVAL_HOLD for this run for (j = 0; j < driver_count; j++) L6470.set_param(axis_index[j], L6470_KVAL_HOLD, kval_hold); // turn the motor(s) both directions sprintf_P(gcode_string, PSTR("G0 %s%4.3f F%4.3f"), temp_axis_string, position_min, final_feedrate); gcode.process_subcommands_now_P(gcode_string); sprintf_P(gcode_string, PSTR("G0 %s%4.3f F%4.3f"), temp_axis_string, position_max, final_feedrate); gcode.process_subcommands_now_P(gcode_string); // get the status after the motors have stopped planner.synchronize(); status_composite = 0; // clear out the old bits for (j = 0; j < driver_count; j++) { axis_status[j] = (~L6470.get_status(axis_index[j])) & L6470_ERROR_MASK; // bits of interest are all active low status_composite |= axis_status[j] ; } if (status_composite && (status_composite & STATUS_UVLO)) { L6470_ECHOLNPGM("Test aborted (Undervoltage lockout active)"); for (j = 0; j < driver_count; j++) { L6470_ECHOPGM("..."); L6470.error_status_decode(axis_status[j], axis_index[j]); } return; } // increment KVAL_HOLD if not yet at thermal warning/shutdown if (!(status_composite & (STATUS_TH_WRN | STATUS_TH_SD))) kval_hold++; } while (!(status_composite & (STATUS_TH_WRN | STATUS_TH_SD)) && kval_hold); // exit when kval_hold == 0 (rolls over) L6470_ECHOPGM(".\n.\nThermal warning/shutdown "); if ((status_composite & (STATUS_TH_WRN | STATUS_TH_SD))) { L6470_ECHOLNPGM("has occurred"); for (j = 0; j < driver_count; j++) { L6470_ECHOPGM("..."); L6470.error_status_decode(axis_status[j], axis_index[j]); } } else L6470_ECHOLNPGM("(Unable to get)"); L6470_ECHOLNPGM("."); } /** * * M917: Find minimum current thresholds * * Decrease OCD current until overcurrent error * Increase OCD until overcurrent error goes away * Decrease stall threshold until stall * Increase stall until stall error goes away * * J - select which driver(s) to monitor on multi-driver axis * 0 - (default) monitor all drivers on the axis or E0 * 1 - monitor only X, Y, Z, E1 * 2 - monitor only X2, Y2, Z2, E2 * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement * xxx (1-255) is distance moved on either side of current position * * F - feedrate * optional - will use default max feedrate from Configuration.h if not specified * * I - starting over-current threshold * optional - will report current value from driver if not specified * if there are multiple drivers on the axis then all will be set the same * * K - value for KVAL_HOLD (0 - 255) * optional - will report current value from driver if not specified * */ void GcodeSuite::M917() { L6470_ECHOLNPGM("M917"); char axis_mon[3][3] = { " ", " ", " " }; // list of axes to be monitored uint8_t axis_index[3]; uint16_t axis_status[3]; uint8_t driver_count = 1; float position_max; float position_min; float final_feedrate; uint8_t kval_hold; uint8_t ocd_th_val = 0; uint8_t stall_th_val = 0; uint16_t over_current_threshold; constexpr bool over_current_flag = true; uint8_t j; // general purpose counter if (L6470.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, ocd_th_val, stall_th_val, over_current_threshold)) return; // quit if invalid user input L6470_ECHOLNPAIR("feedrate = ", final_feedrate); planner.synchronize(); // wait for all current movement commands to complete for (j = 0; j < driver_count; j++) L6470.get_status(axis_index[j]); // clear out any pre-existing error flags char temp_axis_string[] = " "; temp_axis_string[0] = axis_mon[0][0]; // need to have a string for use within sprintf format section char gcode_string[80]; uint16_t status_composite = 0; uint8_t test_phase = 0; // 0 - decreasing OCD - exit when OCD warning occurs (ignore STALL) // 1 - increasing OCD - exit when OCD warning stops (ignore STALL) - // 2 - OCD finalized - decreasing STALL - exit when STALL warning happens // 3 - OCD finalized - increasing STALL - exit when STALL warning stop // 4 - all testing completed L6470_ECHOPAIR(".\n.\n.\nover_current threshold : ", (ocd_th_val + 1) * 375); // first status display L6470_ECHOPAIR(" (OCD_TH: : ", ocd_th_val); L6470_ECHOPAIR(") Stall threshold: ", (stall_th_val + 1) * 31.25); L6470_ECHOPAIR(" (STALL_TH: ", stall_th_val); L6470_ECHOLNPGM(")"); do { L6470_ECHOPAIR("STALL threshold : ", (stall_th_val + 1) * 31.25); L6470_ECHOLNPAIR(" OCD threshold : ", (ocd_th_val + 1) * 375); sprintf_P(gcode_string, PSTR("G0 %s%4.3f F%4.3f"), temp_axis_string, position_min, final_feedrate); gcode.process_subcommands_now_P(gcode_string); sprintf_P(gcode_string, PSTR("G0 %s%4.3f F%4.3f"), temp_axis_string, position_max, final_feedrate); gcode.process_subcommands_now_P(gcode_string); planner.synchronize(); status_composite = 0; // clear out the old bits for (j = 0; j < driver_count; j++) { axis_status[j] = (~L6470.get_status(axis_index[j])) & L6470_ERROR_MASK; // bits of interest are all active low status_composite |= axis_status[j]; } if (status_composite && (status_composite & STATUS_UVLO)) { L6470_ECHOLNPGM("Test aborted (Undervoltage lockout active)"); for (j = 0; j < driver_count; j++) { L6470_ECHOPGM("..."); L6470.error_status_decode(axis_status[j], axis_index[j]); } return; } if (status_composite & (STATUS_TH_WRN | STATUS_TH_SD)) { L6470_ECHOLNPGM("thermal problem - waiting for chip(s) to cool down "); uint16_t status_composite_temp = 0; uint8_t k = 0; do { k++; if (!(k % 4)) { kval_hold *= 0.95; L6470_EOL(); L6470_ECHOLNPAIR("Lowering KVAL_HOLD by about 5% to ", kval_hold); for (j = 0; j < driver_count; j++) L6470.set_param(axis_index[j], L6470_KVAL_HOLD, kval_hold); } L6470_ECHOLNPGM("."); gcode.reset_stepper_timeout(); // reset_stepper_timeout to keep steppers powered watchdog_reset(); // beat the dog safe_delay(5000); status_composite_temp = 0; for (j = 0; j < driver_count; j++) { axis_status[j] = (~L6470.get_status(axis_index[j])) & L6470_ERROR_MASK; // bits of interest are all active low status_composite_temp |= axis_status[j]; } } while (status_composite_temp & (STATUS_TH_WRN | STATUS_TH_SD)); L6470_EOL(); } if (status_composite & (STATUS_STEP_LOSS_A | STATUS_STEP_LOSS_B | STATUS_OCD)) { switch (test_phase) { case 0: { if (status_composite & STATUS_OCD) { // phase 0 with OCD warning - time to go to next phase if (ocd_th_val >=15) { ocd_th_val = 15; // limit to max test_phase = 2; // at highest value so skip phase 1 L6470_ECHOLNPGM("LOGIC E0A OCD at highest - skip to 2"); } else { ocd_th_val++; // normal exit to next phase test_phase = 1; // setup for first pass of phase 1 L6470_ECHOLNPGM("LOGIC E0B - inc OCD & go to 1"); } } else { // phase 0 without OCD warning - keep on decrementing if can if (ocd_th_val) { ocd_th_val--; // try lower value L6470_ECHOLNPGM("LOGIC E0C - dec OCD"); } else { test_phase = 2; // at lowest value without warning so skip phase 1 L6470_ECHOLNPGM("LOGIC E0D - OCD at latest - go to 2"); } } } break; case 1: { if (status_composite & STATUS_OCD) { // phase 1 with OCD warning - increment if can if (ocd_th_val >= 15) { ocd_th_val = 15; // limit to max test_phase = 2; // at highest value so go to next phase L6470_ECHOLNPGM("LOGIC E1A - OCD at max - go to 2"); } else { ocd_th_val++; // try a higher value L6470_ECHOLNPGM("LOGIC E1B - inc OCD"); } } else { // phase 1 without OCD warning - normal exit to phase 2 test_phase = 2; L6470_ECHOLNPGM("LOGIC E1C - no OCD warning - go to 1"); } } break; case 2: { if (status_composite & (STATUS_STEP_LOSS_A | STATUS_STEP_LOSS_B)) { // phase 2 with stall warning - time to go to next phase if (stall_th_val >= 127) { stall_th_val = 127; // limit to max L6470_ECHOLNPGM("LOGIC E2A - STALL warning, STALL at max, quit"); L6470_ECHOLNPGM("finished - STALL at maximum value but still have stall warning"); test_phase = 4; } else { test_phase = 3; // normal exit to next phase (found failing value of STALL) stall_th_val++; // setup for first pass of phase 3 L6470_ECHOLNPGM("LOGIC E2B - INC - STALL warning, inc Stall, go to 3"); } } else { // phase 2 without stall warning - decrement if can if (stall_th_val) { stall_th_val--; // try a lower value L6470_ECHOLNPGM("LOGIC E2C - no STALL, dec STALL"); } else { L6470_ECHOLNPGM("finished - STALL at lowest value but still do NOT have stall warning"); test_phase = 4; L6470_ECHOLNPGM("LOGIC E2D - no STALL, at lowest so quit"); } } } break; case 3: { if (status_composite & (STATUS_STEP_LOSS_A | STATUS_STEP_LOSS_B)) { // phase 3 with stall warning - increment if can if (stall_th_val >= 127) { stall_th_val = 127; // limit to max L6470_ECHOLNPGM("finished - STALL at maximum value but still have stall warning"); test_phase = 4; L6470_ECHOLNPGM("LOGIC E3A - STALL, at max so quit"); } else { stall_th_val++; // still looking for passing value L6470_ECHOLNPGM("LOGIC E3B - STALL, inc stall"); } } else { //phase 3 without stall warning but have OCD warning L6470_ECHOLNPGM("Hardware problem - OCD warning without STALL warning"); test_phase = 4; L6470_ECHOLNPGM("LOGIC E3C - not STALLED, hardware problem (quit)"); } } break; } } else { switch (test_phase) { case 0: { // phase 0 without OCD warning - keep on decrementing if can if (ocd_th_val) { ocd_th_val--; // try lower value L6470_ECHOLNPGM("LOGIC N0A - DEC OCD"); } else { test_phase = 2; // at lowest value without warning so skip phase 1 L6470_ECHOLNPGM("LOGIC N0B - OCD at lowest (go to phase 2)"); } } break; case 1: L6470_ECHOLNPGM("LOGIC N1 (go directly to 2)"); // phase 1 without OCD warning - drop directly to phase 2 case 2: { // phase 2 without stall warning - keep on decrementing if can if (stall_th_val) { stall_th_val--; // try a lower value (stay in phase 2) L6470_ECHOLNPGM("LOGIC N2B - dec STALL"); } else { L6470_ECHOLNPGM("finished - STALL at lowest value but still no stall warning"); test_phase = 4; L6470_ECHOLNPGM("LOGIC N2C - STALL at lowest (quit)"); } } break; case 3: { test_phase = 4; L6470_ECHOLNPGM("LOGIC N3 - finished!"); } break; // phase 3 without any warnings - desired exit } // } // end of status checks if (test_phase != 4) { for (j = 0; j < driver_count; j++) { // update threshold(s) L6470.set_param(axis_index[j], L6470_OCD_TH, ocd_th_val); L6470.set_param(axis_index[j], L6470_STALL_TH, stall_th_val); if (L6470.get_param(axis_index[j], L6470_OCD_TH) != ocd_th_val) L6470_ECHOLNPGM("OCD mismatch"); if (L6470.get_param(axis_index[j], L6470_STALL_TH) != stall_th_val) L6470_ECHOLNPGM("STALL mismatch"); } } } while (test_phase != 4); if (status_composite) { L6470_ECHOLNPGM("Completed with errors"); for (j = 0; j < driver_count; j++) { L6470_ECHOPGM("..."); L6470.error_status_decode(axis_status[j], axis_index[j]); } } else L6470_ECHOLNPGM("Completed with no errors"); } // M917 /** * * M918: increase speed until error or max feedrate achieved (as shown in configuration.h)) * * J - select which driver(s) to monitor on multi-driver axis * 0 - (default) monitor all drivers on the axis or E0 * 1 - monitor only X, Y, Z, E1 * 2 - monitor only X2, Y2, Z2, E2 * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement * xxx (1-255) is distance moved on either side of current position * * I - over current threshold * optional - will report current value from driver if not specified * * K - value for KVAL_HOLD (0 - 255) (optional) * optional - will report current value from driver if not specified * */ void GcodeSuite::M918() { L6470_ECHOLNPGM("M918"); char axis_mon[3][3] = { " ", " ", " " }; // List of axes to monitor uint8_t axis_index[3]; uint16_t axis_status[3]; uint8_t driver_count = 1; float position_max, position_min; float final_feedrate; uint8_t kval_hold; uint8_t ocd_th_val = 0; uint8_t stall_th_val = 0; uint16_t over_current_threshold; constexpr bool over_current_flag = true; uint8_t j; // general purpose counter if (L6470.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, ocd_th_val, stall_th_val, over_current_threshold)) return; // quit if invalid user input uint8_t m_steps = parser.byteval('M'); LIMIT(m_steps, 0, 128); L6470_ECHOLNPAIR("M = ", m_steps); int8_t m_bits = -1; if (m_steps > 85) m_bits = 7; // 128 (no synch output) else if (m_steps > 42) m_bits = 6; // 64 (no synch output) else if (m_steps > 22) m_bits = 5; // 32 (no synch output) else if (m_steps > 12) m_bits = 4; // 16 (no synch output) else if (m_steps > 5) m_bits = 3; // 8 (no synch output) else if (m_steps > 2) m_bits = 2; // 4 (no synch output) else if (m_steps == 2) m_bits = 1; // 2 (no synch output) else if (m_steps == 1) m_bits = 0; // 1 (no synch output) else if (m_steps == 0) m_bits = 7; // 128 (no synch output) if (m_bits >= 0) { const int micros = _BV(m_bits); if (micros < 100) { L6470_CHAR(' '); if (micros < 10) L6470_CHAR(' '); } L6470_ECHO(micros); L6470_ECHOPGM(" uSTEPS"); } for (j = 0; j < driver_count; j++) L6470.set_param(axis_index[j], L6470_STEP_MODE, m_bits); // set microsteps L6470_ECHOLNPAIR("target (maximum) feedrate = ",final_feedrate); float feedrate_inc = final_feedrate / 10, // start at 1/10 of max & go up by 1/10 per step) current_feedrate = 0; planner.synchronize(); // wait for all current movement commands to complete for (j = 0; j < driver_count; j++) L6470.get_status(axis_index[j]); // clear all error flags char temp_axis_string[2]; temp_axis_string[0] = axis_mon[0][0]; // need to have a string for use within sprintf format section temp_axis_string[1] = '\n'; char gcode_string[80]; uint16_t status_composite = 0; L6470_ECHOLNPGM(".\n.\n."); // make the feedrate prints easier to see do { current_feedrate += feedrate_inc; L6470_ECHOLNPAIR("...feedrate = ", current_feedrate); sprintf_P(gcode_string, PSTR("G0 %s%4.3f F%4.3f"), temp_axis_string, position_min, current_feedrate); gcode.process_subcommands_now_P(gcode_string); sprintf_P(gcode_string, PSTR("G0 %s%4.3f F%4.3f"), temp_axis_string, position_max, current_feedrate); gcode.process_subcommands_now_P(gcode_string); planner.synchronize(); for (j = 0; j < driver_count; j++) { axis_status[j] = (~L6470.get_status(axis_index[j])) & 0x0800; // bits of interest are all active low status_composite |= axis_status[j]; } if (status_composite) break; // quit if any errors flags are raised } while (current_feedrate < final_feedrate * 0.99); if (status_composite) { L6470_ECHOLNPGM("Completed with errors"); for (j = 0; j < driver_count; j++) { L6470_ECHOPGM("..."); L6470.error_status_decode(axis_status[j], axis_index[j]); } } else L6470_ECHOLNPGM("Completed with no errors"); } // M918 #endif // HAS_DRIVER(L6470)