mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2025-01-31 14:12:52 +00:00
parent
d94defc545
commit
6fe387b6f0
34 changed files with 181 additions and 165 deletions
|
@ -1429,12 +1429,10 @@
|
|||
|
||||
#endif // HAS_MARLINUI_MENU
|
||||
|
||||
#if ANY(HAS_DISPLAY, DWIN_LCD_PROUI, DWIN_CREALITY_LCD_JYERSUI)
|
||||
#if HAS_DISPLAY
|
||||
//#define SOUND_MENU_ITEM // Add a mute option to the LCD menu
|
||||
#define SOUND_ON_DEFAULT // Buzzer/speaker default enabled state
|
||||
#endif
|
||||
|
||||
#if EITHER(HAS_DISPLAY, DWIN_LCD_PROUI)
|
||||
// The timeout to return to the status screen from sub-menus
|
||||
//#define LCD_TIMEOUT_TO_STATUS 15000 // (ms)
|
||||
|
||||
|
@ -1483,7 +1481,7 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#endif // HAS_DISPLAY || DWIN_LCD_PROUI
|
||||
#endif // HAS_DISPLAY
|
||||
|
||||
// Add 'M73' to set print job progress, overrides Marlin's built-in estimate
|
||||
//#define SET_PROGRESS_MANUALLY
|
||||
|
|
|
@ -91,7 +91,7 @@ void GcodeSuite::M428() {
|
|||
diff[i] = -current_position[i];
|
||||
if (!WITHIN(diff[i], -20, 20)) {
|
||||
SERIAL_ERROR_MSG(STR_ERR_M428_TOO_FAR);
|
||||
LCD_ALERTMESSAGE_F("Err: Too far!");
|
||||
LCD_ALERTMESSAGE(MSG_ERR_M428_TOO_FAR);
|
||||
ERR_BUZZ();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ void GcodeSuite::M303() {
|
|||
default:
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_BAD_HEATER_ID);
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_BAD_EXTRUDER_NUM));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(PID_BAD_EXTRUDER_NUM));
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_BAD_HEATER_ID));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_BAD_HEATER_ID));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -78,20 +78,9 @@ void GcodeSuite::M303() {
|
|||
const celsius_t temp = seenS ? parser.value_celsius() : default_temp;
|
||||
const bool u = parser.boolval('U');
|
||||
|
||||
#if ENABLED(DWIN_LCD_PROUI) && EITHER(PIDTEMP, PIDTEMPBED)
|
||||
if (seenC) HMI_data.PidCycles = c;
|
||||
if (seenS) {
|
||||
switch (hid) {
|
||||
OPTCODE(PIDTEMP, case 0 ... HOTENDS - 1: HMI_data.HotendPidT = temp; break)
|
||||
OPTCODE(PIDTEMPBED, case H_BED: HMI_data.BedPidT = temp; break)
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
TERN_(DWIN_PID_TUNE, DWIN_StartM303(seenC, c, seenS, hid, temp));
|
||||
|
||||
#if DISABLED(BUSY_WHILE_HEATING)
|
||||
KEEPALIVE_STATE(NOT_BUSY);
|
||||
#endif
|
||||
IF_DISABLED(BUSY_WHILE_HEATING, KEEPALIVE_STATE(NOT_BUSY));
|
||||
|
||||
LCD_MESSAGE(MSG_PID_AUTOTUNE);
|
||||
thermalManager.PID_autotune(temp, hid, c, u);
|
||||
|
|
|
@ -52,15 +52,15 @@ void GcodeSuite::M306() {
|
|||
|
||||
if (parser.seen("ACFPRH")) {
|
||||
const heater_id_t hid = (heater_id_t)parser.intval('E', 0);
|
||||
MPC_t &constants = thermalManager.temp_hotend[hid].constants;
|
||||
if (parser.seenval('P')) constants.heater_power = parser.value_float();
|
||||
if (parser.seenval('C')) constants.block_heat_capacity = parser.value_float();
|
||||
if (parser.seenval('R')) constants.sensor_responsiveness = parser.value_float();
|
||||
if (parser.seenval('A')) constants.ambient_xfer_coeff_fan0 = parser.value_float();
|
||||
MPC_t &mpc = thermalManager.temp_hotend[hid].mpc;
|
||||
if (parser.seenval('P')) mpc.heater_power = parser.value_float();
|
||||
if (parser.seenval('C')) mpc.block_heat_capacity = parser.value_float();
|
||||
if (parser.seenval('R')) mpc.sensor_responsiveness = parser.value_float();
|
||||
if (parser.seenval('A')) mpc.ambient_xfer_coeff_fan0 = parser.value_float();
|
||||
#if ENABLED(MPC_INCLUDE_FAN)
|
||||
if (parser.seenval('F')) constants.fan255_adjustment = parser.value_float() - constants.ambient_xfer_coeff_fan0;
|
||||
if (parser.seenval('F')) mpc.applyFanAdjustment(parser.value_float());
|
||||
#endif
|
||||
if (parser.seenval('H')) constants.filament_heat_capacity_permm = parser.value_float();
|
||||
if (parser.seenval('H')) mpc.filament_heat_capacity_permm = parser.value_float();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71,16 +71,16 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) {
|
|||
report_heading(forReplay, F("Model predictive control"));
|
||||
HOTEND_LOOP() {
|
||||
report_echo_start(forReplay);
|
||||
MPC_t& constants = thermalManager.temp_hotend[e].constants;
|
||||
MPC_t &mpc = thermalManager.temp_hotend[e].mpc;
|
||||
SERIAL_ECHOPGM(" M306 E", e);
|
||||
SERIAL_ECHOPAIR_F(" P", constants.heater_power, 2);
|
||||
SERIAL_ECHOPAIR_F(" C", constants.block_heat_capacity, 2);
|
||||
SERIAL_ECHOPAIR_F(" R", constants.sensor_responsiveness, 4);
|
||||
SERIAL_ECHOPAIR_F(" A", constants.ambient_xfer_coeff_fan0, 4);
|
||||
SERIAL_ECHOPAIR_F(" P", mpc.heater_power, 2);
|
||||
SERIAL_ECHOPAIR_F(" C", mpc.block_heat_capacity, 2);
|
||||
SERIAL_ECHOPAIR_F(" R", mpc.sensor_responsiveness, 4);
|
||||
SERIAL_ECHOPAIR_F(" A", mpc.ambient_xfer_coeff_fan0, 4);
|
||||
#if ENABLED(MPC_INCLUDE_FAN)
|
||||
SERIAL_ECHOPAIR_F(" F", constants.ambient_xfer_coeff_fan0 + constants.fan255_adjustment, 4);
|
||||
SERIAL_ECHOPAIR_F(" F", mpc.fanCoefficient(), 4);
|
||||
#endif
|
||||
SERIAL_ECHOPAIR_F(" H", constants.filament_heat_capacity_permm, 4);
|
||||
SERIAL_ECHOPAIR_F(" H", mpc.filament_heat_capacity_permm, 4);
|
||||
SERIAL_EOL();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2504,6 +2504,9 @@
|
|||
// PID heating
|
||||
#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER)
|
||||
#define HAS_PID_HEATING 1
|
||||
#if ENABLED(DWIN_LCD_PROUI) && EITHER(PIDTEMP, PIDTEMPBED)
|
||||
#define DWIN_PID_TUNE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Thermal protection
|
||||
|
|
|
@ -1500,9 +1500,10 @@ void DWIN_LevelingDone() {
|
|||
|
||||
// PID process
|
||||
|
||||
#if HAS_PIDPLOT
|
||||
#if BOTH(HAS_PIDPLOT, DWIN_PID_TUNE)
|
||||
|
||||
void DWIN_Draw_PIDPopup() {
|
||||
frame_rect_t gfrm = {40, 180, DWIN_WIDTH - 80, 120};
|
||||
frame_rect_t gfrm = { 40, 180, DWIN_WIDTH - 80, 120 };
|
||||
DWINUI::ClearMainArea();
|
||||
Draw_Popup_Bkgd();
|
||||
DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 100, GET_TEXT_F(MSG_PID_AUTOTUNE));
|
||||
|
@ -1525,9 +1526,21 @@ void DWIN_LevelingDone() {
|
|||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if EITHER(PIDTEMP, PIDTEMPBED)
|
||||
#if DWIN_PID_TUNE
|
||||
|
||||
void DWIN_StartM303(const bool seenC, const int c, const bool seenS, const heater_id_t hid, const celsius_t temp) {
|
||||
if (seenC) HMI_data.PidCycles = c;
|
||||
if (seenS) {
|
||||
switch (hid) {
|
||||
OPTCODE(PIDTEMP, case 0 ... HOTENDS - 1: HMI_data.HotendPidT = temp; break)
|
||||
OPTCODE(PIDTEMPBED, case H_BED: HMI_data.BedPidT = temp; break)
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DWIN_PidTuning(pidresult_t result) {
|
||||
HMI_value.pidresult = result;
|
||||
|
@ -1541,9 +1554,9 @@ void DWIN_LevelingDone() {
|
|||
DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT_F(MSG_PID_AUTOTUNE), F("for Nozzle is running."));
|
||||
#endif
|
||||
break;
|
||||
case PID_BAD_EXTRUDER_NUM:
|
||||
case PID_BAD_HEATER_ID:
|
||||
checkkey = last_checkkey;
|
||||
DWIN_Popup_Confirm(ICON_TempTooLow, GET_TEXT_F(MSG_PID_AUTOTUNE_FAILED), GET_TEXT_F(MSG_BAD_EXTRUDER_NUM));
|
||||
DWIN_Popup_Confirm(ICON_TempTooLow, GET_TEXT_F(MSG_PID_AUTOTUNE_FAILED), GET_TEXT_F(MSG_BAD_HEATER_ID));
|
||||
break;
|
||||
#endif
|
||||
#if ENABLED(PIDTEMPBED)
|
||||
|
@ -1568,11 +1581,13 @@ void DWIN_LevelingDone() {
|
|||
checkkey = last_checkkey;
|
||||
DWIN_Popup_Confirm(ICON_TempTooLow, GET_TEXT_F(MSG_PID_AUTOTUNE), GET_TEXT_F(MSG_BUTTON_DONE));
|
||||
break;
|
||||
default: checkkey = last_checkkey; break;
|
||||
default:
|
||||
checkkey = last_checkkey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PIDTEMP || PIDTEMPBED
|
||||
#endif // DWIN_PID_TUNE
|
||||
|
||||
// Started a Print Job
|
||||
void DWIN_Print_Started() {
|
||||
|
@ -1667,7 +1682,7 @@ void DWIN_SetDataDefaults() {
|
|||
DWINUI::SetColors(HMI_data.Text_Color, HMI_data.Background_Color, HMI_data.StatusBg_Color);
|
||||
TERN_(PIDTEMP, HMI_data.HotendPidT = DEF_HOTENDPIDT);
|
||||
TERN_(PIDTEMPBED, HMI_data.BedPidT = DEF_BEDPIDT);
|
||||
TERN_(HAS_PID_HEATING, HMI_data.PidCycles = DEF_PIDCYCLES);
|
||||
TERN_(DWIN_PID_TUNE, HMI_data.PidCycles = DEF_PIDCYCLES);
|
||||
#if ENABLED(PREVENT_COLD_EXTRUSION)
|
||||
HMI_data.ExtMinT = EXTRUDE_MINTEMP;
|
||||
ApplyExtMinT();
|
||||
|
@ -2057,7 +2072,7 @@ void SetMoveZ() { HMI_value.axis = Z_AXIS; SetPFloatOnClick(Z_MIN_POS, Z_MAX_POS
|
|||
}
|
||||
#endif
|
||||
|
||||
#if EITHER(PIDTEMP, PIDTEMPBED)
|
||||
#if DWIN_PID_TUNE
|
||||
void SetPID(celsius_t t, heater_id_t h) {
|
||||
char cmd[53] = "";
|
||||
char str_1[5] = "", str_2[5] = "";
|
||||
|
@ -2523,7 +2538,7 @@ void SetStepsZ() { HMI_value.axis = Z_AXIS, SetPFloatOnClick( MIN_STEP, MAX_STEP
|
|||
void SetBedPidT() { SetPIntOnClick(MIN_BEDTEMP, MAX_BEDTEMP); }
|
||||
#endif
|
||||
|
||||
#if EITHER(PIDTEMP, PIDTEMPBED)
|
||||
#if DWIN_PID_TUNE
|
||||
void SetPidCycles() { SetPIntOnClick(3, 50); }
|
||||
void SetKp() { SetPFloatOnClick(0, 1000, 2); }
|
||||
void ApplyPIDi() {
|
||||
|
@ -2694,7 +2709,7 @@ void onDrawGetColorItem(MenuItemClass* menuitem, int8_t line) {
|
|||
DWIN_Draw_HLine(HMI_data.SplitLine_Color, 16, MYPOS(line + 1), 240);
|
||||
}
|
||||
|
||||
#if EITHER(PIDTEMP, PIDTEMPBED)
|
||||
#if DWIN_PID_TUNE
|
||||
void onDrawPIDi(MenuItemClass* menuitem, int8_t line) { onDrawFloatMenu(menuitem, line, 2, unscalePID_i(*(float*)static_cast<MenuItemPtrClass*>(menuitem)->value)); }
|
||||
void onDrawPIDd(MenuItemClass* menuitem, int8_t line) { onDrawFloatMenu(menuitem, line, 2, unscalePID_d(*(float*)static_cast<MenuItemPtrClass*>(menuitem)->value)); }
|
||||
#endif
|
||||
|
|
|
@ -76,7 +76,7 @@ enum processID : uint8_t {
|
|||
enum pidresult_t : uint8_t {
|
||||
PIDTEMP_START = 0,
|
||||
PIDTEMPBED_START,
|
||||
PID_BAD_EXTRUDER_NUM,
|
||||
PID_BAD_HEATER_ID,
|
||||
PID_TEMP_TOO_HIGH,
|
||||
PID_TUNING_TIMEOUT,
|
||||
PID_DONE,
|
||||
|
@ -107,14 +107,14 @@ typedef struct {
|
|||
uint16_t Coordinate_Color;
|
||||
|
||||
// Temperatures
|
||||
#if ENABLED(PIDTEMP)
|
||||
int16_t HotendPidT = DEF_HOTENDPIDT;
|
||||
#endif
|
||||
#if ENABLED(PIDTEMPBED)
|
||||
int16_t BedPidT = DEF_BEDPIDT;
|
||||
#endif
|
||||
#if (HAS_HOTEND || HAS_HEATED_BED) && HAS_PID_HEATING
|
||||
#if DWIN_PID_TUNE
|
||||
int16_t PidCycles = DEF_PIDCYCLES;
|
||||
#if ENABLED(PIDTEMP)
|
||||
int16_t HotendPidT = DEF_HOTENDPIDT;
|
||||
#endif
|
||||
#if ENABLED(PIDTEMPBED)
|
||||
int16_t BedPidT = DEF_BEDPIDT;
|
||||
#endif
|
||||
#endif
|
||||
#if ENABLED(PREVENT_COLD_EXTRUSION)
|
||||
int16_t ExtMinT = EXTRUDE_MINTEMP;
|
||||
|
@ -141,8 +141,8 @@ static constexpr size_t eeprom_data_size = sizeof(HMI_data_t);
|
|||
|
||||
typedef struct {
|
||||
int8_t Color[3]; // Color components
|
||||
#if HAS_PID_HEATING
|
||||
tempcontrol_t pidresult = PID_DONE;
|
||||
#if DWIN_PID_TUNE
|
||||
pidresult_t pidresult = PID_DONE;
|
||||
#endif
|
||||
uint8_t Select = 0; // Auxiliary selector variable
|
||||
AxisEnum axis = X_AXIS; // Axis Select
|
||||
|
@ -358,8 +358,10 @@ void Draw_Steps_Menu();
|
|||
#endif
|
||||
|
||||
// PID
|
||||
#if HAS_PID_HEATING
|
||||
void DWIN_PidTuning(tempcontrol_t result);
|
||||
#if DWIN_PID_TUNE
|
||||
#include "../../../module/temperature.h"
|
||||
void DWIN_StartM303(const bool seenC, const int c, const bool seenS, const heater_id_t hid, const celsius_t temp);
|
||||
void DWIN_PidTuning(pidresult_t result);
|
||||
#if ENABLED(PIDTEMP)
|
||||
void Draw_HotendPID_Menu();
|
||||
#endif
|
||||
|
@ -367,4 +369,3 @@ void Draw_Steps_Menu();
|
|||
void Draw_BedPID_Menu();
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -74,7 +74,7 @@ void PlotClass::Draw(const frame_rect_t frame, const float max, const float ref)
|
|||
DWIN_Draw_HLine(Color_Red, frame.x, r, frame.w);
|
||||
}
|
||||
|
||||
void PlotClass::Update(const float value) {
|
||||
void PlotClass::Update(const_float_t value) {
|
||||
if (!scale) return;
|
||||
uint16_t y = round((y2) - value * scale);
|
||||
if (grphpoints < grphframe.w) {
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
class PlotClass {
|
||||
public:
|
||||
void Draw(frame_rect_t frame, float max, float ref = 0);
|
||||
void Update(float value);
|
||||
void Update(const_float_t value);
|
||||
};
|
||||
|
||||
extern PlotClass Plot;
|
||||
|
|
|
@ -295,11 +295,11 @@ void DGUSScreenHandler::DGUSLCD_SendHeaterStatusToDisplay(DGUS_VP_Variable &var)
|
|||
|
||||
// Don't let the user in the dark why there is no reaction.
|
||||
if (!ExtUI::isMediaInserted()) {
|
||||
setstatusmessagePGM(GET_TEXT(MSG_NO_MEDIA));
|
||||
setstatusmessage(GET_TEXT_F(MSG_NO_MEDIA));
|
||||
return;
|
||||
}
|
||||
if (card.flag.abort_sd_printing) {
|
||||
setstatusmessagePGM(GET_TEXT(MSG_MEDIA_ABORTING));
|
||||
setstatusmessage(GET_TEXT_F(MSG_MEDIA_ABORTING));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
static void setstatusmessage(const char *msg);
|
||||
// The same for messages from Flash
|
||||
static void setstatusmessagePGM(PGM_P const msg);
|
||||
static void setstatusmessage(FSTR_P const fmsg) { setstatusmessagePGM(FTOP(fmsg)); }
|
||||
// Callback for VP "Display wants to change screen on idle printer"
|
||||
static void ScreenChangeHookIfIdle(DGUS_VP_Variable &var, void *val_ptr);
|
||||
// Callback for VP "Screen has been changed"
|
||||
|
|
|
@ -137,19 +137,19 @@ namespace ExtUI {
|
|||
// Called for temperature PID tuning result
|
||||
switch (rst) {
|
||||
case PID_STARTED:
|
||||
ScreenHandler.setstatusmessagePGM(GET_TEXT(MSG_PID_AUTOTUNE));
|
||||
ScreenHandler.setstatusmessage(GET_TEXT_F(MSG_PID_AUTOTUNE));
|
||||
break;
|
||||
case PID_BAD_EXTRUDER_NUM:
|
||||
ScreenHandler.setstatusmessagePGM(GET_TEXT(MSG_PID_BAD_EXTRUDER_NUM));
|
||||
case PID_BAD_HEATER_ID:
|
||||
ScreenHandler.setstatusmessage(GET_TEXT_F(MSG_PID_BAD_HEATER_ID));
|
||||
break;
|
||||
case PID_TEMP_TOO_HIGH:
|
||||
ScreenHandler.setstatusmessagePGM(GET_TEXT(MSG_PID_TEMP_TOO_HIGH));
|
||||
ScreenHandler.setstatusmessage(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH));
|
||||
break;
|
||||
case PID_TUNING_TIMEOUT:
|
||||
ScreenHandler.setstatusmessagePGM(GET_TEXT(MSG_PID_TIMEOUT));
|
||||
ScreenHandler.setstatusmessage(GET_TEXT_F(MSG_PID_TIMEOUT));
|
||||
break;
|
||||
case PID_DONE:
|
||||
ScreenHandler.setstatusmessagePGM(GET_TEXT(MSG_PID_AUTOTUNE_DONE));
|
||||
ScreenHandler.setstatusmessage(GET_TEXT_F(MSG_PID_AUTOTUNE_DONE));
|
||||
break;
|
||||
}
|
||||
ScreenHandler.GotoScreen(DGUSLCD_SCREEN_MAIN);
|
||||
|
|
|
@ -325,8 +325,8 @@ void DGUSScreenHandler::FilamentRunout(const ExtUI::extruder_t extruder) {
|
|||
case ExtUI::PID_STARTED:
|
||||
SetStatusMessage(GET_TEXT_F(MSG_PID_AUTOTUNE));
|
||||
break;
|
||||
case ExtUI::PID_BAD_EXTRUDER_NUM:
|
||||
SetStatusMessage(GET_TEXT_F(MSG_PID_BAD_EXTRUDER_NUM));
|
||||
case ExtUI::PID_BAD_HEATER_ID:
|
||||
SetStatusMessage(GET_TEXT_F(MSG_PID_BAD_HEATER_ID));
|
||||
break;
|
||||
case ExtUI::PID_TEMP_TOO_HIGH:
|
||||
SetStatusMessage(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH));
|
||||
|
|
|
@ -72,10 +72,10 @@ public:
|
|||
static void SetMessageLine(const char* msg, uint8_t line);
|
||||
static void SetMessageLinePGM(PGM_P msg, uint8_t line);
|
||||
|
||||
static void SetStatusMessage(const char* msg, const millis_t duration = DGUS_STATUS_EXPIRATION_MS);
|
||||
static void SetStatusMessage(FSTR_P const msg, const millis_t duration = DGUS_STATUS_EXPIRATION_MS);
|
||||
static void SetStatusMessage(const char* msg, const millis_t duration=DGUS_STATUS_EXPIRATION_MS);
|
||||
static void SetStatusMessage(FSTR_P const msg, const millis_t duration=DGUS_STATUS_EXPIRATION_MS);
|
||||
|
||||
static void ShowWaitScreen(DGUS_Screen return_screen, bool has_continue = false);
|
||||
static void ShowWaitScreen(DGUS_Screen return_screen, bool has_continue=false);
|
||||
|
||||
static DGUS_Screen GetCurrentScreen();
|
||||
static void TriggerScreenChange(DGUS_Screen screen);
|
||||
|
@ -115,7 +115,7 @@ private:
|
|||
static const DGUS_Addr* FindScreenAddrList(DGUS_Screen screen);
|
||||
static bool CallScreenSetup(DGUS_Screen screen);
|
||||
|
||||
static void MoveToScreen(DGUS_Screen screen, bool abort_wait = false);
|
||||
static void MoveToScreen(DGUS_Screen screen, bool abort_wait=false);
|
||||
static bool SendScreenVPData(DGUS_Screen screen, bool complete_update);
|
||||
|
||||
static bool settings_ready;
|
||||
|
|
|
@ -121,11 +121,11 @@ namespace ExtUI {
|
|||
void onPidTuning(const result_t rst) {
|
||||
// Called for temperature PID tuning result
|
||||
switch (rst) {
|
||||
case PID_STARTED: break;
|
||||
case PID_BAD_EXTRUDER_NUM: break;
|
||||
case PID_TEMP_TOO_HIGH: break;
|
||||
case PID_TUNING_TIMEOUT: break;
|
||||
case PID_DONE: break;
|
||||
case PID_STARTED: break;
|
||||
case PID_BAD_HEATER_ID: break;
|
||||
case PID_TEMP_TOO_HIGH: break;
|
||||
case PID_TUNING_TIMEOUT: break;
|
||||
case PID_DONE: break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -136,8 +136,8 @@ namespace ExtUI {
|
|||
case PID_STARTED:
|
||||
StatusScreen::setStatusMessage(GET_TEXT_F(MSG_PID_AUTOTUNE));
|
||||
break;
|
||||
case PID_BAD_EXTRUDER_NUM:
|
||||
StatusScreen::setStatusMessage(GET_TEXT_F(MSG_PID_BAD_EXTRUDER_NUM));
|
||||
case PID_BAD_HEATER_ID:
|
||||
StatusScreen::setStatusMessage(GET_TEXT_F(MSG_PID_BAD_HEATER_ID));
|
||||
break;
|
||||
case PID_TEMP_TOO_HIGH:
|
||||
StatusScreen::setStatusMessage(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH));
|
||||
|
|
|
@ -111,8 +111,8 @@ namespace ExtUI {
|
|||
case PID_STARTED:
|
||||
set_lcd_error(GET_TEXT_F(MSG_PID_AUTOTUNE));
|
||||
break;
|
||||
case PID_BAD_EXTRUDER_NUM:
|
||||
set_lcd_error(GET_TEXT_F(MSG_PID_BAD_EXTRUDER_NUM));
|
||||
case PID_BAD_HEATER_ID:
|
||||
set_lcd_error(GET_TEXT_F(MSG_PID_BAD_HEATER_ID));
|
||||
break;
|
||||
case PID_TEMP_TOO_HIGH:
|
||||
set_lcd_error(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH));
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace ExtUI {
|
|||
enum extruder_t : uint8_t { E0, E1, E2, E3, E4, E5, E6, E7 };
|
||||
enum heater_t : uint8_t { H0, H1, H2, H3, H4, H5, BED, CHAMBER, COOLER };
|
||||
enum fan_t : uint8_t { FAN0, FAN1, FAN2, FAN3, FAN4, FAN5, FAN6, FAN7 };
|
||||
enum result_t : uint8_t { PID_STARTED, PID_BAD_EXTRUDER_NUM, PID_TEMP_TOO_HIGH, PID_TUNING_TIMEOUT, PID_DONE };
|
||||
enum result_t : uint8_t { PID_STARTED, PID_BAD_HEATER_ID, PID_TEMP_TOO_HIGH, PID_TUNING_TIMEOUT, PID_DONE };
|
||||
|
||||
constexpr uint8_t extruderCount = EXTRUDERS;
|
||||
constexpr uint8_t hotendCount = HOTENDS;
|
||||
|
|
|
@ -343,10 +343,10 @@ namespace Language_de {
|
|||
LSTR MSG_PID_CYCLE = _UxGT("PID Zyklus");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("PID Tuning fertig");
|
||||
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("PID Autotune fehlge.!");
|
||||
LSTR MSG_BAD_EXTRUDER_NUM = _UxGT("ungültiger Extruder.");
|
||||
LSTR MSG_BAD_HEATER_ID = _UxGT("ungültiger Extruder.");
|
||||
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Temperatur zu hoch.");
|
||||
LSTR MSG_TIMEOUT = _UxGT("Timeout.");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Autotune fehlge.! Ungültiger Extruder");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Autotune fehlge.! Ungültiger Extruder");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Autotune fehlge.! Temperatur zu hoch.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Autotune fehlge.! Timeout.");
|
||||
LSTR MSG_MPC_MEASURING_AMBIENT = _UxGT("teste Wärmeverlust");
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace Language_en {
|
|||
LSTR MSG_HOME_OFFSET_Y = _UxGT("Home Offset Y");
|
||||
LSTR MSG_HOME_OFFSET_Z = _UxGT("Home Offset Z");
|
||||
LSTR MSG_HOME_OFFSETS_APPLIED = _UxGT("Offsets Applied");
|
||||
LSTR MSG_ERR_M428_TOO_FAR = _UxGT("Err: Too far!");
|
||||
LSTR MSG_TRAMMING_WIZARD = _UxGT("Tramming Wizard");
|
||||
LSTR MSG_SELECT_ORIGIN = _UxGT("Select Origin");
|
||||
LSTR MSG_LAST_VALUE_SP = _UxGT("Last value ");
|
||||
|
@ -364,20 +365,22 @@ namespace Language_en {
|
|||
LSTR MSG_PID_CYCLE = _UxGT("PID Cycles");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("PID tuning done");
|
||||
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("PID Autotune failed!");
|
||||
LSTR MSG_BAD_EXTRUDER_NUM = _UxGT("Bad extruder.");
|
||||
LSTR MSG_BAD_HEATER_ID = _UxGT("Bad extruder.");
|
||||
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Temperature too high.");
|
||||
LSTR MSG_TIMEOUT = _UxGT("Timeout.");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Autotune failed! Bad extruder.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Autotune failed! Bad extruder.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Autotune failed! Temperature too high.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Autotune failed! Timeout.");
|
||||
LSTR MSG_MPC_MEASURING_AMBIENT = _UxGT("Testing heat loss");
|
||||
LSTR MSG_MPC_HEATING_PAST_200 = _UxGT("Heating to >200C");
|
||||
LSTR MSG_MPC_COOLING_TO_AMBIENT = _UxGT("Cooling to ambient");
|
||||
LSTR MSG_MPC_AUTOTUNE = _UxGT("MPC Autotune");
|
||||
LSTR MSG_MPC_EDIT = _UxGT("Edit * MPC");
|
||||
LSTR MSG_MPC_POWER_E = _UxGT("Power *");
|
||||
LSTR MSG_MPC_BLOCK_HEAT_CAPACITY_E = _UxGT("Block C *");
|
||||
LSTR MSG_SENSOR_RESPONSIVENESS_E = _UxGT("Sensor res *");
|
||||
LSTR MSG_MPC_AMBIENT_XFER_COEFF_E = _UxGT("Ambient h *");
|
||||
LSTR MSG_MPC_AMBIENT_XFER_COEFF_FAN_E = _UxGT("Amb. h fan *");
|
||||
LSTR MSG_MPC_BLOCK_HEAT_CAPACITY_E = _UxGT("Heat Cap. *");
|
||||
LSTR MSG_SENSOR_RESPONSIVENESS_E = _UxGT("Sensor Resp. *");
|
||||
LSTR MSG_MPC_AMBIENT_XFER_COEFF_E = _UxGT("Ambient Co. *");
|
||||
LSTR MSG_MPC_AMBIENT_XFER_COEFF_FAN_E = _UxGT("Fan coeff. *");
|
||||
LSTR MSG_SELECT_E = _UxGT("Select *");
|
||||
LSTR MSG_ACC = _UxGT("Accel");
|
||||
LSTR MSG_JERK = _UxGT("Jerk");
|
||||
|
|
|
@ -269,7 +269,7 @@ namespace Language_fr {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("PID Autotune");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("PID Autotune *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("Tuning PID terminé");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Echec Autotune! E incorrect");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Echec Autotune! E incorrect");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Echec Autotune! Temp. trop haute");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Echec Autotune! Opér. expirée");
|
||||
LSTR MSG_SELECT_E = _UxGT("Sélectionner *");
|
||||
|
|
|
@ -263,7 +263,7 @@ namespace Language_gl {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("Auto-Sint. PID");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("Auto-Sint. PID *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("Fin Auto-Sint PID");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Auto-Sint. fallida! Extrusor danado.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Auto-Sint. fallida! Extrusor danado.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Auto-Sint. fallida! Temperatura moi alta.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Auto-Sint. fallida! Tempo excedido.");
|
||||
LSTR MSG_SELECT_E = _UxGT("Escolla *");
|
||||
|
|
|
@ -306,7 +306,7 @@ namespace Language_hu {
|
|||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("PID hangolás *");
|
||||
LSTR MSG_PID_CYCLE = _UxGT("PID ciklus");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("PID hangolás kész");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Hangolási hiba! Rossz adagoló.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Hangolási hiba! Rossz adagoló.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Hangolási hiba! Magas hömérséklet.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Hangolási hiba! Idötúllépés.");
|
||||
LSTR MSG_SELECT_E = _UxGT("Kiválaszt *");
|
||||
|
|
|
@ -356,10 +356,10 @@ namespace Language_it {
|
|||
LSTR MSG_PID_CYCLE = _UxGT("Ciclo PID");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("Calibr.PID eseguita");
|
||||
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("Calibr.PID fallito!");
|
||||
LSTR MSG_BAD_EXTRUDER_NUM = _UxGT("Estrusore invalido.");
|
||||
LSTR MSG_BAD_HEATER_ID = _UxGT("Estrusore invalido.");
|
||||
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Temp.troppo alta.");
|
||||
LSTR MSG_TIMEOUT = _UxGT("Tempo scaduto.");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Calibrazione fallita! Estrusore errato.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Calibrazione fallita! Estrusore errato.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Calibrazione fallita! Temperatura troppo alta.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Calibrazione fallita! Tempo scaduto.");
|
||||
LSTR MSG_MPC_MEASURING_AMBIENT = _UxGT("Testing heat loss");
|
||||
|
|
|
@ -261,7 +261,7 @@ namespace Language_ro {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("PID Autotune");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("PID Autotune *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("PID tuning done");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Autotune failed! Bad extruder.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Autotune failed! Bad extruder.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Autotune failed! Temperature too high.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Autotune failed! Timeout.");
|
||||
LSTR MSG_SELECT_E = _UxGT("Select *");
|
||||
|
|
|
@ -382,7 +382,7 @@ namespace Language_ru {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("Автоподбор PID");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("Автоподбор PID *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("Подбор PID выполнен");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Сбой автоподбора! Плохой экструдер.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Сбой автоподбора! Плохой экструдер.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Сбой автоподбора! Температура повышена.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Сбой автоподбора! Завершение времени.");
|
||||
|
||||
|
|
|
@ -354,10 +354,10 @@ namespace Language_sk {
|
|||
LSTR MSG_PID_CYCLE = _UxGT("Cykly PID");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("Kal. PID dokončená");
|
||||
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("Kal. PID zlyhala!");
|
||||
LSTR MSG_BAD_EXTRUDER_NUM = _UxGT("Zlý extrudér");
|
||||
LSTR MSG_BAD_HEATER_ID = _UxGT("Zlý extrudér");
|
||||
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Príliš vysoká tepl.");
|
||||
LSTR MSG_TIMEOUT = _UxGT("Čas vypršal.");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Auto-kal. zlyhala! Zlý extrúder.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Auto-kal. zlyhala! Zlý extrúder.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Auto-kal. zlyhala! Príliš vysoká tepl.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Auto-kal. zlyhala! Čas vypršal.");
|
||||
LSTR MSG_MPC_MEASURING_AMBIENT = _UxGT("Test. tepl. straty");
|
||||
|
|
|
@ -291,7 +291,7 @@ namespace Language_sv {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("PID Autojustera");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("PID Autojustera *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("PID tuning done");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Autojustera misslyckad! Dålig extruder.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Autojustera misslyckad! Dålig extruder.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Autojustera misslyckad! Temperatur för hög.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Autojustera misslyckad! Tidsgräns.");
|
||||
LSTR MSG_SELECT_E = _UxGT("Välj *");
|
||||
|
|
|
@ -387,7 +387,7 @@ namespace Language_uk {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("Автопідбір PID");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("Автопідбір PID *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("Підбір PID виконано");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Збій автопідбору! Поганий екструдер.");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Збій автопідбору! Поганий екструдер.");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Збій автопідбору! Температура завищена.");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("Збій автопідбору! Вичерпан час.");
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ namespace Language_zh_CN {
|
|||
LSTR MSG_PID_AUTOTUNE = _UxGT("自动PID");
|
||||
LSTR MSG_PID_AUTOTUNE_E = _UxGT("自动PID *");
|
||||
LSTR MSG_PID_AUTOTUNE_DONE = _UxGT("PID调整完成");
|
||||
LSTR MSG_PID_BAD_EXTRUDER_NUM = _UxGT("自动调失败! 坏的挤出机");
|
||||
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("自动调失败! 坏的挤出机");
|
||||
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("自动调失败! 温度太高");
|
||||
LSTR MSG_PID_TIMEOUT = _UxGT("自动调失败! 超时");
|
||||
LSTR MSG_SELECT_E = _UxGT("选择 *");
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
#include "../../module/probe.h"
|
||||
#endif
|
||||
|
||||
#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER)
|
||||
#if HAS_PID_HEATING
|
||||
#include "../../module/temperature.h"
|
||||
#endif
|
||||
|
||||
|
@ -277,10 +277,10 @@ void menu_backlash();
|
|||
//
|
||||
#if SHOW_MENU_ADVANCED_TEMPERATURE
|
||||
|
||||
#if ENABLED(MPC_EDIT_MENU)
|
||||
#define MPC_EDIT_DEFS(N) \
|
||||
MPC_t &c = thermalManager.temp_hotend[N].constants; \
|
||||
TERN_(MPC_INCLUDE_FAN, editable.decimal = c.ambient_xfer_coeff_fan0 + c.fan255_adjustment)
|
||||
#if BOTH(MPC_EDIT_MENU, MPC_INCLUDE_FAN)
|
||||
#define MPC_EDIT_DEFS(N) editable.decimal = thermalManager.temp_hotend[N].fanCoefficient()
|
||||
#else
|
||||
#define MPC_EDIT_DEFS(...)
|
||||
#endif
|
||||
|
||||
void menu_advanced_temperature() {
|
||||
|
@ -370,17 +370,17 @@ void menu_backlash();
|
|||
#if ENABLED(MPC_EDIT_MENU)
|
||||
|
||||
#define _MPC_EDIT_ITEMS(N) \
|
||||
EDIT_ITEM_FAST_N(float31sign, N, MSG_MPC_POWER_E, &c.heater_power, 1, 200); \
|
||||
EDIT_ITEM_FAST_N(float31sign, N, MSG_MPC_BLOCK_HEAT_CAPACITY_E, &c.block_heat_capacity, 0, 40); \
|
||||
EDIT_ITEM_FAST_N(float43, N, MSG_SENSOR_RESPONSIVENESS_E, &c.sensor_responsiveness, 0, 1); \
|
||||
EDIT_ITEM_FAST_N(float43, N, MSG_MPC_AMBIENT_XFER_COEFF_E, &c.ambient_xfer_coeff_fan0, 0, 1)
|
||||
EDIT_ITEM_FAST_N(float31sign, N, MSG_MPC_POWER_E, &mpc.heater_power, 1, 200); \
|
||||
EDIT_ITEM_FAST_N(float31sign, N, MSG_MPC_BLOCK_HEAT_CAPACITY_E, &mpc.block_heat_capacity, 0, 40); \
|
||||
EDIT_ITEM_FAST_N(float43, N, MSG_SENSOR_RESPONSIVENESS_E, &mpc.sensor_responsiveness, 0, 1); \
|
||||
EDIT_ITEM_FAST_N(float43, N, MSG_MPC_AMBIENT_XFER_COEFF_E, &mpc.ambient_xfer_coeff_fan0, 0, 1)
|
||||
|
||||
#if ENABLED(MPC_INCLUDE_FAN)
|
||||
#define MPC_EDIT_ITEMS(N) \
|
||||
MPC_t &mpc = thermalManager.temp_hotend[MenuItemBase::itemIndex].constants; \
|
||||
_MPC_EDIT_ITEMS(N); \
|
||||
EDIT_ITEM_FAST_N(float43, N, MSG_MPC_AMBIENT_XFER_COEFF_FAN_E, &editable.decimal, 0, 1, []{ \
|
||||
MPC_t &c = thermalManager.temp_hotend[MenuItemBase::itemIndex].constants; \
|
||||
c.fan255_adjustment = editable.decimal - c.ambient_xfer_coeff_fan0; \
|
||||
thermalManager.temp_hotend[MenuItemBase::itemIndex].applyFanAdjustment(editable.decimal); \
|
||||
})
|
||||
#else
|
||||
#define MPC_EDIT_ITEMS _MPC_EDIT_ITEMS
|
||||
|
|
|
@ -1632,8 +1632,7 @@ void MarlinSettings::postprocess() {
|
|||
// Model predictive control
|
||||
//
|
||||
#if ENABLED(MPCTEMP)
|
||||
HOTEND_LOOP()
|
||||
EEPROM_WRITE(thermalManager.temp_hotend[e].constants);
|
||||
HOTEND_LOOP() EEPROM_WRITE(thermalManager.temp_hotend[e].mpc);
|
||||
#endif
|
||||
|
||||
//
|
||||
|
@ -2627,8 +2626,7 @@ void MarlinSettings::postprocess() {
|
|||
//
|
||||
#if ENABLED(MPCTEMP)
|
||||
{
|
||||
HOTEND_LOOP()
|
||||
EEPROM_READ(thermalManager.temp_hotend[e].constants);
|
||||
HOTEND_LOOP() EEPROM_READ(thermalManager.temp_hotend[e].mpc);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -3416,15 +3414,15 @@ void MarlinSettings::reset() {
|
|||
static_assert(COUNT(_filament_heat_capacity_permm) == HOTENDS, "FILAMENT_HEAT_CAPACITY_PERMM must have HOTENDS items.");
|
||||
|
||||
HOTEND_LOOP() {
|
||||
MPC_t &constants = thermalManager.temp_hotend[e].constants;
|
||||
constants.heater_power = _mpc_heater_power[e];
|
||||
constants.block_heat_capacity = _mpc_block_heat_capacity[e];
|
||||
constants.sensor_responsiveness = _mpc_sensor_responsiveness[e];
|
||||
constants.ambient_xfer_coeff_fan0 = _mpc_ambient_xfer_coeff[e];
|
||||
MPC_t &mpc = thermalManager.temp_hotend[e].mpc;
|
||||
mpc.heater_power = _mpc_heater_power[e];
|
||||
mpc.block_heat_capacity = _mpc_block_heat_capacity[e];
|
||||
mpc.sensor_responsiveness = _mpc_sensor_responsiveness[e];
|
||||
mpc.ambient_xfer_coeff_fan0 = _mpc_ambient_xfer_coeff[e];
|
||||
#if ENABLED(MPC_INCLUDE_FAN)
|
||||
constants.fan255_adjustment = _mpc_ambient_xfer_coeff_fan255[e] - _mpc_ambient_xfer_coeff[e];
|
||||
mpc.fan255_adjustment = _mpc_ambient_xfer_coeff_fan255[e] - _mpc_ambient_xfer_coeff[e];
|
||||
#endif
|
||||
constants.filament_heat_capacity_permm = _filament_heat_capacity_permm[e];
|
||||
mpc.filament_heat_capacity_permm = _filament_heat_capacity_permm[e];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -660,13 +660,13 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
TERN_(HAS_FAN_LOGIC, fan_update_ms = next_temp_ms + fan_update_interval_ms);
|
||||
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_STARTED));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(isbed ? PIDTEMPBED_START : PIDTEMP_START));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(isbed ? PIDTEMPBED_START : PIDTEMP_START));
|
||||
|
||||
if (target > GHV(CHAMBER_MAX_TARGET, BED_MAX_TARGET, temp_range[heater_id].maxtemp - (HOTEND_OVERSHOOT))) {
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH)));
|
||||
return;
|
||||
}
|
||||
|
@ -760,7 +760,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH)));
|
||||
break;
|
||||
}
|
||||
|
@ -797,7 +797,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
#endif
|
||||
if ((ms - _MIN(t1, t2)) > (MAX_CYCLE_TIME_PID_AUTOTUNE * 60L * 1000L)) {
|
||||
TERN_(DWIN_CREALITY_LCD, DWIN_Popup_Temperature(0));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(PID_TUNING_TIMEOUT));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_TUNING_TIMEOUT));
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TUNING_TIMEOUT));
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_TIMEOUT)));
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
|
@ -852,7 +852,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
TERN_(PRINTER_EVENT_LEDS, printerEventLEDs.onPidTuningDone(color));
|
||||
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_DONE));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(PID_DONE));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_DONE));
|
||||
|
||||
goto EXIT_M303;
|
||||
}
|
||||
|
@ -870,7 +870,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
TERN_(PRINTER_EVENT_LEDS, printerEventLEDs.onPidTuningDone(color));
|
||||
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_DONE));
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_PidTuning(PID_DONE));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_DONE));
|
||||
|
||||
EXIT_M303:
|
||||
TERN_(NO_FAN_SLOWING_IN_PID_TUNING, adaptive_fan_slowing = true);
|
||||
|
@ -882,7 +882,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
#if ENABLED(MPCTEMP)
|
||||
|
||||
void Temperature::MPC_autotune() {
|
||||
auto housekeeping = [] (millis_t& ms, celsius_float_t& current_temp, millis_t& next_report_ms) {
|
||||
auto housekeeping = [] (millis_t &ms, celsius_float_t ¤t_temp, millis_t &next_report_ms) {
|
||||
ms = millis();
|
||||
|
||||
if (updateTemperaturesIfReady()) { // temp sample ready
|
||||
|
@ -929,7 +929,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
SERIAL_ECHOPGM(STR_MPC_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_START, active_extruder);
|
||||
MPCHeaterInfo &hotend = temp_hotend[active_extruder];
|
||||
MPC_t &constants = hotend.constants;
|
||||
MPC_t &mpc = hotend.mpc;
|
||||
|
||||
// Move to center of bed, just above bed height and cool with max fan
|
||||
gcode.home_all_axes(true);
|
||||
|
@ -939,11 +939,11 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
set_fan_speed(EITHER(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255);
|
||||
planner.sync_fan_speeds(fan_speed);
|
||||
#endif
|
||||
const xyz_pos_t tuningpos = MPC_TUNING_POS;
|
||||
do_blocking_move_to(tuningpos);
|
||||
do_blocking_move_to(xyz_pos_t(MPC_TUNING_POS));
|
||||
|
||||
SERIAL_ECHOLNPGM(STR_MPC_COOLING_TO_AMBIENT);
|
||||
LCD_MESSAGE(MSG_COOLING);
|
||||
|
||||
millis_t ms = millis(), next_report_ms = ms, next_test_ms = ms + 10000UL;
|
||||
celsius_float_t current_temp = degHotend(active_extruder),
|
||||
ambient_temp = current_temp;
|
||||
|
@ -961,6 +961,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
next_test_ms += 10000UL;
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
|
||||
#if HAS_FAN
|
||||
set_fan_speed(EITHER(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0);
|
||||
|
@ -970,7 +971,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
hotend.modeled_ambient_temp = ambient_temp;
|
||||
|
||||
SERIAL_ECHOLNPGM(STR_MPC_HEATING_PAST_200);
|
||||
LCD_MESSAGE(MSG_HEATING);
|
||||
TERN(DWIN_LCD_PROUI, LCD_ALERTMESSAGE(MSG_MPC_HEATING_PAST_200), LCD_MESSAGE(MSG_HEATING));
|
||||
hotend.target = 200.0f; // So M105 looks nice
|
||||
hotend.soft_pwm_amount = MPC_MAX >> 1;
|
||||
const millis_t heat_start_time = next_test_ms = ms;
|
||||
|
@ -1012,17 +1013,17 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
float asymp_temp = (t2 * t2 - t1 * t3) / (2 * t2 - t1 - t3),
|
||||
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1));
|
||||
|
||||
constants.ambient_xfer_coeff_fan0 = constants.heater_power * (MPC_MAX) / 255 / (asymp_temp - ambient_temp);
|
||||
constants.fan255_adjustment = 0.0f;
|
||||
constants.block_heat_capacity = constants.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
constants.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
|
||||
mpc.ambient_xfer_coeff_fan0 = mpc.heater_power * (MPC_MAX) / 255 / (asymp_temp - ambient_temp);
|
||||
mpc.fan255_adjustment = 0.0f;
|
||||
mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
mpc.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
|
||||
|
||||
hotend.modeled_block_temp = asymp_temp + (ambient_temp - asymp_temp) * exp(-block_responsiveness * (ms - heat_start_time) / 1000.0f);
|
||||
hotend.modeled_sensor_temp = current_temp;
|
||||
|
||||
// Allow the system to stabilize under MPC, then get a better measure of ambient loss with and without fan
|
||||
SERIAL_ECHOLNPGM(STR_MPC_MEASURING_AMBIENT, hotend.modeled_block_temp);
|
||||
LCD_MESSAGE(MSG_MPC_MEASURING_AMBIENT);
|
||||
TERN(DWIN_LCD_PROUI, LCD_ALERTMESSAGE(MSG_MPC_MEASURING_AMBIENT), LCD_MESSAGE(MSG_MPC_MEASURING_AMBIENT));
|
||||
hotend.target = hotend.modeled_block_temp;
|
||||
next_test_ms = ms + MPC_dT * 1000;
|
||||
constexpr millis_t settle_time = 20000UL, test_duration = 20000UL;
|
||||
|
@ -1042,7 +1043,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
hotend.soft_pwm_amount = (int)get_pid_output_hotend(active_extruder) >> 1;
|
||||
|
||||
if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms) && TERN1(HAS_FAN, !fan0_done))
|
||||
total_energy_fan0 += constants.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * constants.block_heat_capacity;
|
||||
total_energy_fan0 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity;
|
||||
#if HAS_FAN
|
||||
else if (ELAPSED(ms, test_end_ms) && !fan0_done) {
|
||||
set_fan_speed(EITHER(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255);
|
||||
|
@ -1052,7 +1053,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
fan0_done = true;
|
||||
}
|
||||
else if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms))
|
||||
total_energy_fan255 += constants.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * constants.block_heat_capacity;
|
||||
total_energy_fan255 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity;
|
||||
#endif
|
||||
else if (ELAPSED(ms, test_end_ms)) break;
|
||||
|
||||
|
@ -1067,24 +1068,24 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
}
|
||||
|
||||
const float power_fan0 = total_energy_fan0 * 1000 / test_duration;
|
||||
constants.ambient_xfer_coeff_fan0 = power_fan0 / (hotend.target - ambient_temp);
|
||||
mpc.ambient_xfer_coeff_fan0 = power_fan0 / (hotend.target - ambient_temp);
|
||||
|
||||
#if HAS_FAN
|
||||
const float power_fan255 = total_energy_fan255 * 1000 / test_duration,
|
||||
ambient_xfer_coeff_fan255 = power_fan255 / (hotend.target - ambient_temp);
|
||||
constants.fan255_adjustment = ambient_xfer_coeff_fan255 - constants.ambient_xfer_coeff_fan0;
|
||||
mpc.applyFanAdjustment(ambient_xfer_coeff_fan255);
|
||||
#endif
|
||||
|
||||
// Calculate a new and better asymptotic temperature and re-evaluate the other constants
|
||||
asymp_temp = ambient_temp + constants.heater_power * (MPC_MAX) / 255 / constants.ambient_xfer_coeff_fan0;
|
||||
asymp_temp = ambient_temp + mpc.heater_power * (MPC_MAX) / 255 / mpc.ambient_xfer_coeff_fan0;
|
||||
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1));
|
||||
constants.block_heat_capacity = constants.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
constants.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
|
||||
mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
mpc.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
|
||||
|
||||
SERIAL_ECHOPGM(STR_MPC_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_FINISHED);
|
||||
|
||||
/* <-- add a slash to enable
|
||||
#if 0
|
||||
SERIAL_ECHOLNPGM("t1_time ", t1_time);
|
||||
SERIAL_ECHOLNPGM("sample_count ", sample_count);
|
||||
SERIAL_ECHOLNPGM("sample_distance ", sample_distance);
|
||||
|
@ -1093,10 +1094,11 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
SERIAL_ECHOLNPGM("t1 ", t1, " t2 ", t2, " t3 ", t3);
|
||||
SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
|
||||
SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
|
||||
//*/
|
||||
SERIAL_ECHOLNPGM("MPC_BLOCK_HEAT_CAPACITY ", constants.block_heat_capacity);
|
||||
SERIAL_ECHOLNPAIR_F("MPC_SENSOR_RESPONSIVENESS ", constants.sensor_responsiveness, 4);
|
||||
SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF ", constants.ambient_xfer_coeff_fan0, 4);
|
||||
#endif
|
||||
|
||||
SERIAL_ECHOLNPGM("MPC_BLOCK_HEAT_CAPACITY ", mpc.block_heat_capacity);
|
||||
SERIAL_ECHOLNPAIR_F("MPC_SENSOR_RESPONSIVENESS ", mpc.sensor_responsiveness, 4);
|
||||
SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF ", mpc.ambient_xfer_coeff_fan0, 4);
|
||||
TERN_(HAS_FAN, SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF_FAN255 ", ambient_xfer_coeff_fan255, 4));
|
||||
}
|
||||
|
||||
|
@ -1413,7 +1415,7 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
#elif ENABLED(MPCTEMP)
|
||||
|
||||
MPCHeaterInfo &hotend = temp_hotend[ee];
|
||||
MPC_t &constants = hotend.constants;
|
||||
MPC_t &mpc = hotend.mpc;
|
||||
|
||||
// At startup, initialize modeled temperatures
|
||||
if (isnan(hotend.modeled_block_temp)) {
|
||||
|
@ -1427,11 +1429,11 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
const bool this_hotend = (ee == active_extruder);
|
||||
#endif
|
||||
|
||||
float ambient_xfer_coeff = constants.ambient_xfer_coeff_fan0;
|
||||
float ambient_xfer_coeff = mpc.ambient_xfer_coeff_fan0;
|
||||
#if ENABLED(MPC_INCLUDE_FAN)
|
||||
const uint8_t fan_index = EITHER(MPC_FAN_0_ACTIVE_HOTEND, MPC_FAN_0_ALL_HOTENDS) ? 0 : ee;
|
||||
const float fan_fraction = TERN_(MPC_FAN_0_ACTIVE_HOTEND, !this_hotend ? 0.0f : ) fan_speed[fan_index] * RECIPROCAL(255);
|
||||
ambient_xfer_coeff += fan_fraction * constants.fan255_adjustment;
|
||||
ambient_xfer_coeff += fan_fraction * mpc.fan255_adjustment;
|
||||
#endif
|
||||
|
||||
if (this_hotend) {
|
||||
|
@ -1442,17 +1444,17 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
if (fabs(e_speed) > planner.settings.max_feedrate_mm_s[E_AXIS])
|
||||
mpc_e_position = e_position;
|
||||
else if (e_speed > 0.0f) { // Ignore retract/recover moves
|
||||
ambient_xfer_coeff += e_speed * constants.filament_heat_capacity_permm;
|
||||
ambient_xfer_coeff += e_speed * mpc.filament_heat_capacity_permm;
|
||||
mpc_e_position = e_position;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the modeled temperatures
|
||||
float blocktempdelta = hotend.soft_pwm_amount * constants.heater_power * (MPC_dT / 127) / constants.block_heat_capacity;
|
||||
blocktempdelta += (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff * MPC_dT / constants.block_heat_capacity;
|
||||
float blocktempdelta = hotend.soft_pwm_amount * mpc.heater_power * (MPC_dT / 127) / mpc.block_heat_capacity;
|
||||
blocktempdelta += (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff * MPC_dT / mpc.block_heat_capacity;
|
||||
hotend.modeled_block_temp += blocktempdelta;
|
||||
|
||||
const float sensortempdelta = (hotend.modeled_block_temp - hotend.modeled_sensor_temp) * (constants.sensor_responsiveness * MPC_dT);
|
||||
const float sensortempdelta = (hotend.modeled_block_temp - hotend.modeled_sensor_temp) * (mpc.sensor_responsiveness * MPC_dT);
|
||||
hotend.modeled_sensor_temp += sensortempdelta;
|
||||
|
||||
// Any delta between hotend.modeled_sensor_temp and hotend.celsius is either model
|
||||
|
@ -1468,11 +1470,11 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
float power = 0.0;
|
||||
if (hotend.target != 0 && !is_idling) {
|
||||
// Plan power level to get to target temperature in 2 seconds
|
||||
power = (hotend.target - hotend.modeled_block_temp) * constants.block_heat_capacity / 2.0f;
|
||||
power = (hotend.target - hotend.modeled_block_temp) * mpc.block_heat_capacity / 2.0f;
|
||||
power -= (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff;
|
||||
}
|
||||
|
||||
float pid_output = power * 254.0f / constants.heater_power + 1.0f; // Ensure correct quantization into a range of 0 to 127
|
||||
float pid_output = power * 254.0f / mpc.heater_power + 1.0f; // Ensure correct quantization into a range of 0 to 127
|
||||
pid_output = constrain(pid_output, 0, MPC_MAX);
|
||||
|
||||
/* <-- add a slash to enable
|
||||
|
|
|
@ -382,10 +382,14 @@ typedef struct { float p, i, d, c, f; } raw_pidcf_t;
|
|||
float block_heat_capacity; // M306 C
|
||||
float sensor_responsiveness; // M306 R
|
||||
float ambient_xfer_coeff_fan0; // M306 A
|
||||
float filament_heat_capacity_permm; // M306 H
|
||||
#if ENABLED(MPC_INCLUDE_FAN)
|
||||
float fan255_adjustment; // M306 F
|
||||
void applyFanAdjustment(const_float_t cf) { fan255_adjustment = cf - ambient_xfer_coeff_fan0; }
|
||||
#else
|
||||
void applyFanAdjustment(const_float_t) {}
|
||||
#endif
|
||||
float filament_heat_capacity_permm; // M306 H
|
||||
float fanCoefficient() { return SUM_TERN(MPC_INCLUDE_FAN, ambient_xfer_coeff_fan0, fan255_adjustment); }
|
||||
} MPC_t;
|
||||
|
||||
#define MPC_dT ((OVERSAMPLENR * float(ACTUAL_ADC_SAMPLES)) / (TEMP_TIMER_FREQUENCY))
|
||||
|
@ -433,10 +437,12 @@ struct PIDHeaterInfo : public HeaterInfo {
|
|||
|
||||
#if ENABLED(MPCTEMP)
|
||||
struct MPCHeaterInfo : public HeaterInfo {
|
||||
MPC_t constants;
|
||||
MPC_t mpc;
|
||||
float modeled_ambient_temp,
|
||||
modeled_block_temp,
|
||||
modeled_sensor_temp;
|
||||
float fanCoefficient() { return mpc.fanCoefficient(); }
|
||||
void applyFanAdjustment(const_float_t cf) { mpc.applyFanAdjustment(cf); }
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Reference in a new issue