/** * Marlin 3D Printer Firmware * Copyright (c) 2020 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_L64XX #if AXIS_COLLISION('I') #error "M906 parameter collision with axis name." #endif #include "../../gcode.h" #include "../../../libs/L64XX/L64XX_Marlin.h" #include "../../../module/stepper/indirection.h" #include "../../../module/planner.h" #define DEBUG_OUT ENABLED(L6470_CHITCHAT) #include "../../../core/debug_out.h" /** * MACRO to fetch information on the items associated with current limiting * and maximum voltage output. * * L6470 can be setup to shutdown if either current threshold is exceeded. * * L6470 output current can not be set directly. It is set indirectly by * setting the maximum effective output voltage. * * Effective output voltage is set by PWM duty cycle. * * Maximum effective output voltage is affected by MANY variables. The main ones are: * KVAL_HOLD * KVAL_RUN * KVAL_ACC * KVAL_DEC * Vs compensation (if enabled) */ void L64XX_report_current(L64XX &motor, const L64XX_axis_t axis) { if (L64xxManager.spi_abort) return; // don't do anything if set_directions() has occurred const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow; const uint16_t status = L64xxManager.get_status(axis); //also populates shadow structure const uint8_t OverCurrent_Threshold = uint8_t(motor.GetParam(L6470_OCD_TH)); auto say_axis_status = [](const L64XX_axis_t axis, const uint16_t status) { L64xxManager.say_axis(axis); #if ENABLED(L6470_CHITCHAT) char tmp[10]; sprintf_P(tmp, PSTR("%4x "), status); DEBUG_ECHOPGM(" status: ", tmp); print_bin(status); #else UNUSED(status); #endif SERIAL_EOL(); }; char temp_buf[10]; switch (sh.STATUS_AXIS_LAYOUT) { case L6470_STATUS_LAYOUT: // L6470 case L6480_STATUS_LAYOUT: { // L6480 & powerstep01 const uint16_t Stall_Threshold = (uint8_t)motor.GetParam(L6470_STALL_TH), motor_status = (status & (STATUS_MOT_STATUS)) >> 5, L6470_ADC_out = motor.GetParam(L6470_ADC_OUT), L6470_ADC_out_limited = constrain(L6470_ADC_out, 8, 24); const float comp_coef = 1600.0f / L6470_ADC_out_limited; const uint16_t MicroSteps = _BV(motor.GetParam(L6470_STEP_MODE) & 0x07); say_axis_status(axis, sh.STATUS_AXIS_RAW); SERIAL_ECHOPGM("...OverCurrent Threshold: "); sprintf_P(temp_buf, PSTR("%2d ("), OverCurrent_Threshold); SERIAL_ECHO(temp_buf); SERIAL_ECHO((OverCurrent_Threshold + 1) * motor.OCD_CURRENT_CONSTANT_INV); SERIAL_ECHOPGM(" mA)"); SERIAL_ECHOPGM(" Stall Threshold: "); sprintf_P(temp_buf, PSTR("%2d ("), Stall_Threshold); SERIAL_ECHO(temp_buf); SERIAL_ECHO((Stall_Threshold + 1) * motor.STALL_CURRENT_CONSTANT_INV); SERIAL_ECHOPGM(" mA)"); SERIAL_ECHOPGM(" Motor Status: "); switch (motor_status) { case 0: SERIAL_ECHOPGM("stopped"); break; case 1: SERIAL_ECHOPGM("accelerating"); break; case 2: SERIAL_ECHOPGM("decelerating"); break; case 3: SERIAL_ECHOPGM("at constant speed"); break; } SERIAL_EOL(); SERIAL_ECHOPGM("...MicroSteps: ", MicroSteps, " ADC_OUT: ", L6470_ADC_out); SERIAL_ECHOPGM(" Vs_compensation: "); SERIAL_ECHOF((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_EN_VSCOMP) ? F("ENABLED ") : F("DISABLED")); SERIAL_ECHOLNPGM(" Compensation coefficient: ~", comp_coef * 0.01f); SERIAL_ECHOPGM("...KVAL_HOLD: ", motor.GetParam(L6470_KVAL_HOLD), " KVAL_RUN : ", motor.GetParam(L6470_KVAL_RUN), " KVAL_ACC: ", motor.GetParam(L6470_KVAL_ACC), " KVAL_DEC: ", motor.GetParam(L6470_KVAL_DEC), " V motor max = "); switch (motor_status) { case 0: SERIAL_ECHO(motor.GetParam(L6470_KVAL_HOLD) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_HOLD)"); break; case 1: SERIAL_ECHO(motor.GetParam(L6470_KVAL_RUN) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_RUN)"); break; case 2: SERIAL_ECHO(motor.GetParam(L6470_KVAL_ACC) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_ACC)"); break; case 3: SERIAL_ECHO(motor.GetParam(L6470_KVAL_DEC) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_HOLD)"); break; } SERIAL_EOL(); #if ENABLED(L6470_CHITCHAT) DEBUG_ECHOPGM("...SLEW RATE: "); switch (sh.STATUS_AXIS_LAYOUT) { case L6470_STATUS_LAYOUT: { switch ((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT) { case 0: { DEBUG_ECHOLNPGM("320V/uS") ; break; } case 1: { DEBUG_ECHOLNPGM("75V/uS") ; break; } case 2: { DEBUG_ECHOLNPGM("110V/uS") ; break; } case 3: { DEBUG_ECHOLNPGM("260V/uS") ; break; } } break; } case L6480_STATUS_LAYOUT: { switch (motor.GetParam(L6470_GATECFG1) & CONFIG1_SR ) { case CONFIG1_SR_220V_us: { DEBUG_ECHOLNPGM("220V/uS") ; break; } case CONFIG1_SR_400V_us: { DEBUG_ECHOLNPGM("400V/uS") ; break; } case CONFIG1_SR_520V_us: { DEBUG_ECHOLNPGM("520V/uS") ; break; } case CONFIG1_SR_980V_us: { DEBUG_ECHOLNPGM("980V/uS") ; break; } default: { DEBUG_ECHOLNPGM("unknown") ; break; } } } } #endif SERIAL_EOL(); break; } case L6474_STATUS_LAYOUT: { // L6474 const uint16_t L6470_ADC_out = motor.GetParam(L6470_ADC_OUT) & 0x1F, L6474_TVAL_val = motor.GetParam(L6474_TVAL) & 0x7F; say_axis_status(axis, sh.STATUS_AXIS_RAW); SERIAL_ECHOPGM("...OverCurrent Threshold: "); sprintf_P(temp_buf, PSTR("%2d ("), OverCurrent_Threshold); SERIAL_ECHO(temp_buf); SERIAL_ECHO((OverCurrent_Threshold + 1) * motor.OCD_CURRENT_CONSTANT_INV); SERIAL_ECHOPGM(" mA)"); SERIAL_ECHOPGM(" TVAL: "); sprintf_P(temp_buf, PSTR("%2d ("), L6474_TVAL_val); SERIAL_ECHO(temp_buf); SERIAL_ECHO((L6474_TVAL_val + 1) * motor.STALL_CURRENT_CONSTANT_INV); SERIAL_ECHOLNPGM(" mA) Motor Status: NA"); const uint16_t MicroSteps = _BV(motor.GetParam(L6470_STEP_MODE) & 0x07); //NOMORE(MicroSteps, 16); SERIAL_ECHOPGM("...MicroSteps: ", MicroSteps, " ADC_OUT: ", L6470_ADC_out); SERIAL_ECHOLNPGM(" Vs_compensation: NA\n"); SERIAL_ECHOLNPGM("...KVAL_HOLD: NA" " KVAL_RUN : NA" " KVAL_ACC: NA" " KVAL_DEC: NA" " V motor max = NA"); #if ENABLED(L6470_CHITCHAT) DEBUG_ECHOPGM("...SLEW RATE: "); switch ((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT) { case 0: DEBUG_ECHOLNPGM("320V/uS") ; break; case 1: DEBUG_ECHOLNPGM("75V/uS") ; break; case 2: DEBUG_ECHOLNPGM("110V/uS") ; break; case 3: DEBUG_ECHOLNPGM("260V/uS") ; break; default: DEBUG_ECHOLNPGM("slew rate: ", (motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT); break; } #endif SERIAL_EOL(); SERIAL_EOL(); break; } } } /** * M906: report or set KVAL_HOLD which sets the maximum effective voltage provided by the * PWMs to the steppers * * On L6474 this sets the TVAL register (same address). * * I - select which driver(s) to change on multi-driver axis * (default) all drivers on the axis * 0 - monitor only the first XYZ... driver * 1 - monitor only X2, Y2, Z2 * 2 - monitor only Z3 * 3 - monitor only Z4 * Xxxx, Yxxx, Zxxx, Exxx - axis to change (optional) * L6474 - current in mA (4A max) * All others - 0-255 * * Sets KVAL_HOLD which affects the current being driven through the stepper. * * L6470 is used in the STEP-CLOCK mode. KVAL_HOLD is the only KVAL_xxx * that affects the effective voltage seen by the stepper. */ void GcodeSuite::M906() { L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status #define L6470_SET_KVAL_HOLD(Q) (AXIS_IS_L64XX(Q) ? stepper##Q.setTVALCurrent(value) : stepper##Q.SetParam(L6470_KVAL_HOLD, uint8_t(value))) DEBUG_ECHOLNPGM("M906"); uint8_t report_current = true; #if AXIS_IS_L64XX(X2) || AXIS_IS_L64XX(Y2) || AXIS_IS_L64XX(Z2) || AXIS_IS_L64XX(Z3) || AXIS_IS_L64XX(Z4) const int8_t index = parser.byteval('I', -1); #else constexpr int8_t index = -1; #endif LOOP_LOGICAL_AXES(i) if (uint16_t value = parser.intval(axis_codes[i])) { report_current = false; if (planner.has_blocks_queued() || planner.cleaning_buffer_counter) { SERIAL_ECHOLNPGM("Test aborted. Can't set KVAL_HOLD while steppers are moving."); return; } switch (i) { #if AXIS_IS_L64XX(X) || AXIS_IS_L64XX(X2) case X_AXIS: #if AXIS_IS_L64XX(X) if (index < 0 || index == 0) L6470_SET_KVAL_HOLD(X); #endif #if AXIS_IS_L64XX(X2) if (index < 0 || index == 1) L6470_SET_KVAL_HOLD(X2); #endif break; #endif #if AXIS_IS_L64XX(Y) || AXIS_IS_L64XX(Y2) case Y_AXIS: #if AXIS_IS_L64XX(Y) if (index < 0 || index == 0) L6470_SET_KVAL_HOLD(Y); #endif #if AXIS_IS_L64XX(Y2) if (index < 0 || index == 1) L6470_SET_KVAL_HOLD(Y2); #endif break; #endif #if AXIS_IS_L64XX(Z) || AXIS_IS_L64XX(Z2) || AXIS_IS_L64XX(Z3) || AXIS_IS_L64XX(Z4) case Z_AXIS: #if AXIS_IS_L64XX(Z) if (index < 0 || index == 0) L6470_SET_KVAL_HOLD(Z); #endif #if AXIS_IS_L64XX(Z2) if (index < 0 || index == 1) L6470_SET_KVAL_HOLD(Z2); #endif #if AXIS_IS_L64XX(Z3) if (index < 0 || index == 2) L6470_SET_KVAL_HOLD(Z3); #endif #if AXIS_IS_L64XX(Z4) if (index < 0 || index == 3) L6470_SET_KVAL_HOLD(Z4); #endif break; #endif #if AXIS_IS_L64XX(E0) || AXIS_IS_L64XX(E1) || AXIS_IS_L64XX(E2) || AXIS_IS_L64XX(E3) || AXIS_IS_L64XX(E4) || AXIS_IS_L64XX(E5) || AXIS_IS_L64XX(E6) || AXIS_IS_L64XX(E7) case E_AXIS: { const int8_t eindex = get_target_e_stepper_from_command(-2); #if AXIS_IS_L64XX(E0) if (eindex < 0 || eindex == 0) L6470_SET_KVAL_HOLD(E0); #endif #if AXIS_IS_L64XX(E1) if (eindex < 0 || eindex == 1) L6470_SET_KVAL_HOLD(E1); #endif #if AXIS_IS_L64XX(E2) if (eindex < 0 || eindex == 2) L6470_SET_KVAL_HOLD(E2); #endif #if AXIS_IS_L64XX(E3) if (eindex < 0 || eindex == 3) L6470_SET_KVAL_HOLD(E3); #endif #if AXIS_IS_L64XX(E4) if (eindex < 0 || eindex == 4) L6470_SET_KVAL_HOLD(E4); #endif #if AXIS_IS_L64XX(E5) if (eindex < 0 || eindex == 5) L6470_SET_KVAL_HOLD(E5); #endif #if AXIS_IS_L64XX(E6) if (eindex < 0 || eindex == 6) L6470_SET_KVAL_HOLD(E6); #endif #if AXIS_IS_L64XX(E7) if (eindex < 0 || eindex == 7) L6470_SET_KVAL_HOLD(E7); #endif } break; #endif } } if (report_current) { #define L64XX_REPORT_CURRENT(Q) L64XX_report_current(stepper##Q, Q) L64xxManager.spi_active = true; // Tell set_directions() a series of SPI transfers is underway #if AXIS_IS_L64XX(X) L64XX_REPORT_CURRENT(X); #endif #if AXIS_IS_L64XX(X2) L64XX_REPORT_CURRENT(X2); #endif #if AXIS_IS_L64XX(Y) L64XX_REPORT_CURRENT(Y); #endif #if AXIS_IS_L64XX(Y2) L64XX_REPORT_CURRENT(Y2); #endif #if AXIS_IS_L64XX(Z) L64XX_REPORT_CURRENT(Z); #endif #if AXIS_IS_L64XX(Z2) L64XX_REPORT_CURRENT(Z2); #endif #if AXIS_IS_L64XX(Z3) L64XX_REPORT_CURRENT(Z3); #endif #if AXIS_IS_L64XX(Z4) L64XX_REPORT_CURRENT(Z4); #endif #if AXIS_IS_L64XX(E0) L64XX_REPORT_CURRENT(E0); #endif #if AXIS_IS_L64XX(E1) L64XX_REPORT_CURRENT(E1); #endif #if AXIS_IS_L64XX(E2) L64XX_REPORT_CURRENT(E2); #endif #if AXIS_IS_L64XX(E3) L64XX_REPORT_CURRENT(E3); #endif #if AXIS_IS_L64XX(E4) L64XX_REPORT_CURRENT(E4); #endif #if AXIS_IS_L64XX(E5) L64XX_REPORT_CURRENT(E5); #endif #if AXIS_IS_L64XX(E6) L64XX_REPORT_CURRENT(E6); #endif #if AXIS_IS_L64XX(E7) L64XX_REPORT_CURRENT(E7); #endif L64xxManager.spi_active = false; // done with all SPI transfers - clear handshake flags L64xxManager.spi_abort = false; L64xxManager.pause_monitor(false); } } #endif // HAS_L64XX