@@ -938,7 +938,7 @@ volatile bool Temperature::raw_temps_ready = false;
938
938
939
939
#endif // HAS_PID_HEATING
940
940
941
- #if ENABLED(MPC_AUTOTUNE )
941
+ #if ENABLED(MPC_AUTOTUNE_FANCY )
942
942
943
943
#if EITHER(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND)
944
944
#define SINGLEFAN 1
@@ -1041,7 +1041,8 @@ volatile bool Temperature::raw_temps_ready = false;
1041
1041
1042
1042
next_test_time_ms += test_interval_ms;
1043
1043
1044
- } else if (current_temp < 200 .0f ) {
1044
+ }
1045
+ else if (current_temp < 200 .0f ) {
1045
1046
// Second regime (after 100deg) measure 3 points to determine asymptotic temperature
1046
1047
1047
1048
// If there are too many samples, space them more widely
@@ -1059,7 +1060,8 @@ volatile bool Temperature::raw_temps_ready = false;
1059
1060
1060
1061
next_test_time_ms += test_interval_ms * sample_distance;
1061
1062
1062
- } else {
1063
+ }
1064
+ else {
1063
1065
// Third regime (after 200deg) finished gathering data so finish
1064
1066
break ;
1065
1067
}
@@ -1302,6 +1304,249 @@ volatile bool Temperature::raw_temps_ready = false;
1302
1304
TERN_ (HAS_FAN, SERIAL_ECHOLNPAIR_F (" MPC_AMBIENT_XFER_COEFF_FAN255 " , ambient_xfer_coeff_fan255, 4 ));
1303
1305
}
1304
1306
1307
+ #elif ENABLED(MPC_AUTOTUNE)
1308
+
1309
+ #if EITHER(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND)
1310
+ #define SINGLEFAN 1
1311
+ #endif
1312
+
1313
+ void Temperature::MPC_autotune (const uint8_t e) {
1314
+ auto housekeeping = [] (millis_t &ms, const uint8_t e, celsius_float_t ¤t_temp, millis_t &next_report_ms) {
1315
+ ms = millis ();
1316
+
1317
+ if (updateTemperaturesIfReady ()) { // temp sample ready
1318
+ current_temp = degHotend (e);
1319
+ TERN_ (HAS_FAN_LOGIC, manage_extruder_fans (ms));
1320
+ }
1321
+
1322
+ if (ELAPSED (ms, next_report_ms)) {
1323
+ next_report_ms += 1000UL ;
1324
+
1325
+ print_heater_states (e);
1326
+ SERIAL_EOL ();
1327
+ }
1328
+
1329
+ hal.idletask ();
1330
+ TERN (DWIN_CREALITY_LCD, DWIN_Update (), ui.update ());
1331
+
1332
+ if (!wait_for_heatup) {
1333
+ SERIAL_ECHOLNPGM (STR_MPC_AUTOTUNE_INTERRUPTED);
1334
+ TERN_ (DWIN_LCD_PROUI, DWIN_MPCTuning (MPC_INTERRUPTED));
1335
+ return true ;
1336
+ }
1337
+
1338
+ return false ;
1339
+ };
1340
+
1341
+ struct OnExit {
1342
+ uint8_t e;
1343
+ OnExit (const uint8_t _e) { this ->e = _e; }
1344
+ ~OnExit () {
1345
+ wait_for_heatup = false ;
1346
+
1347
+ ui.reset_status ();
1348
+
1349
+ temp_hotend[e].target = 0 .0f ;
1350
+ temp_hotend[e].soft_pwm_amount = 0 ;
1351
+ #if HAS_FAN
1352
+ set_fan_speed (TERN (SINGLEFAN, 0 , e), 0 );
1353
+ planner.sync_fan_speeds (fan_speed);
1354
+ #endif
1355
+
1356
+ do_z_clearance (MPC_TUNING_END_Z, false );
1357
+
1358
+ TERN_ (TEMP_TUNING_MAINTAIN_FAN, adaptive_fan_slowing = true );
1359
+ }
1360
+ } on_exit (e);
1361
+
1362
+ SERIAL_ECHOLNPGM (STR_MPC_AUTOTUNE_START, e);
1363
+ MPCHeaterInfo &hotend = temp_hotend[e];
1364
+ MPC_t &mpc = hotend.mpc ;
1365
+
1366
+ TERN_ (TEMP_TUNING_MAINTAIN_FAN, adaptive_fan_slowing = false );
1367
+
1368
+ // Move to center of bed, just above bed height and cool with max fan
1369
+ gcode.home_all_axes (true );
1370
+ disable_all_heaters ();
1371
+ #if HAS_FAN
1372
+ zero_fan_speeds ();
1373
+ set_fan_speed (TERN (SINGLEFAN, 0 , e), 255 );
1374
+ planner.sync_fan_speeds (fan_speed);
1375
+ #endif
1376
+ do_blocking_move_to (xyz_pos_t (MPC_TUNING_POS));
1377
+
1378
+ SERIAL_ECHOLNPGM (STR_MPC_COOLING_TO_AMBIENT);
1379
+ #if ENABLED(DWIN_LCD_PROUI)
1380
+ DWIN_MPCTuning (MPCTEMP_START);
1381
+ LCD_ALERTMESSAGE (MSG_MPC_COOLING_TO_AMBIENT);
1382
+ #else
1383
+ LCD_MESSAGE (MSG_COOLING);
1384
+ #endif
1385
+
1386
+ millis_t ms = millis (), next_report_ms = ms, next_test_ms = ms + 10000UL ;
1387
+ celsius_float_t current_temp = degHotend (e),
1388
+ ambient_temp = current_temp;
1389
+
1390
+ wait_for_heatup = true ;
1391
+ for (;;) { // Can be interrupted with M108
1392
+ if (housekeeping (ms, e, current_temp, next_report_ms)) return ;
1393
+
1394
+ if (ELAPSED (ms, next_test_ms)) {
1395
+ if (current_temp >= ambient_temp) {
1396
+ ambient_temp = (ambient_temp + current_temp) / 2 .0f ;
1397
+ break ;
1398
+ }
1399
+ ambient_temp = current_temp;
1400
+ next_test_ms += 10000UL ;
1401
+ }
1402
+ }
1403
+ wait_for_heatup = false ;
1404
+
1405
+ #if HAS_FAN
1406
+ set_fan_speed (TERN (SINGLEFAN, 0 , e), 0 );
1407
+ planner.sync_fan_speeds (fan_speed);
1408
+ #endif
1409
+
1410
+ hotend.modeled_ambient_temp = ambient_temp;
1411
+
1412
+ SERIAL_ECHOLNPGM (STR_MPC_HEATING_PAST_200);
1413
+ TERN (DWIN_LCD_PROUI, LCD_ALERTMESSAGE (MSG_MPC_HEATING_PAST_200), LCD_MESSAGE (MSG_HEATING));
1414
+ hotend.target = 200 .0f ; // So M105 looks nice
1415
+ hotend.soft_pwm_amount = (MPC_MAX) >> 1 ;
1416
+ const millis_t heat_start_time = next_test_ms = ms;
1417
+ celsius_float_t temp_samples[16 ];
1418
+ uint8_t sample_count = 0 ;
1419
+ uint16_t sample_distance = 1 ;
1420
+ float t1_time = 0 ;
1421
+
1422
+ wait_for_heatup = true ;
1423
+ for (;;) { // Can be interrupted with M108
1424
+ if (housekeeping (ms, e, current_temp, next_report_ms)) return ;
1425
+
1426
+ if (ELAPSED (ms, next_test_ms)) {
1427
+ // Record samples between 100C and 200C
1428
+ if (current_temp >= 100 .0f ) {
1429
+ // If there are too many samples, space them more widely
1430
+ if (sample_count == COUNT (temp_samples)) {
1431
+ for (uint8_t i = 0 ; i < COUNT (temp_samples) / 2 ; i++)
1432
+ temp_samples[i] = temp_samples[i*2 ];
1433
+ sample_count /= 2 ;
1434
+ sample_distance *= 2 ;
1435
+ }
1436
+
1437
+ if (sample_count == 0 ) t1_time = float (ms - heat_start_time) / 1000 .0f ;
1438
+ temp_samples[sample_count++] = current_temp;
1439
+ }
1440
+
1441
+ if (current_temp >= 200 .0f ) break ;
1442
+
1443
+ next_test_ms += 1000UL * sample_distance;
1444
+ }
1445
+ }
1446
+ wait_for_heatup = false ;
1447
+
1448
+ hotend.soft_pwm_amount = 0 ;
1449
+
1450
+ // Calculate physical constants from three equally-spaced samples
1451
+ sample_count = (sample_count + 1 ) / 2 * 2 - 1 ;
1452
+ const float t1 = temp_samples[0 ],
1453
+ t2 = temp_samples[(sample_count - 1 ) >> 1 ],
1454
+ t3 = temp_samples[sample_count - 1 ];
1455
+ float asymp_temp = (t2 * t2 - t1 * t3) / (2 * t2 - t1 - t3),
1456
+ block_responsiveness = -log ((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1 ));
1457
+
1458
+ mpc.ambient_xfer_coeff_fan0 = mpc.heater_power * (MPC_MAX) / 255 / (asymp_temp - ambient_temp);
1459
+ mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
1460
+ mpc.sensor_responsiveness = block_responsiveness / (1 .0f - (ambient_temp - asymp_temp) * exp (-block_responsiveness * t1_time) / (t1 - asymp_temp));
1461
+ TERN_ (MPC_INCLUDE_FAN, mpc.fan255_adjustment = 0 .0f );
1462
+
1463
+ hotend.modeled_block_temp = asymp_temp + (ambient_temp - asymp_temp) * exp (-block_responsiveness * (ms - heat_start_time) / 1000 .0f );
1464
+ hotend.modeled_sensor_temp = current_temp;
1465
+
1466
+ // Allow the system to stabilize under MPC, then get a better measure of ambient loss with and without fan
1467
+ SERIAL_ECHOLNPGM (STR_MPC_MEASURING_AMBIENT, hotend.modeled_block_temp );
1468
+ TERN (DWIN_LCD_PROUI, LCD_ALERTMESSAGE (MSG_MPC_MEASURING_AMBIENT), LCD_MESSAGE (MSG_MPC_MEASURING_AMBIENT));
1469
+ hotend.target = hotend.modeled_block_temp ;
1470
+ next_test_ms = ms + MPC_dT * 1000 ;
1471
+ constexpr millis_t settle_time = 20000UL , test_duration = 20000UL ;
1472
+ millis_t settle_end_ms = ms + settle_time,
1473
+ test_end_ms = settle_end_ms + test_duration;
1474
+ float total_energy_fan0 = 0 .0f ;
1475
+ #if HAS_FAN
1476
+ bool fan0_done = false ;
1477
+ float total_energy_fan255 = 0 .0f ;
1478
+ #endif
1479
+ float last_temp = current_temp;
1480
+
1481
+ wait_for_heatup = true ;
1482
+ for (;;) { // Can be interrupted with M108
1483
+ if (housekeeping (ms, e, current_temp, next_report_ms)) return ;
1484
+
1485
+ if (ELAPSED (ms, next_test_ms)) {
1486
+ hotend.soft_pwm_amount = (int )get_pid_output_hotend (e) >> 1 ;
1487
+
1488
+ if (ELAPSED (ms, settle_end_ms) && !ELAPSED (ms, test_end_ms) && TERN1 (HAS_FAN, !fan0_done))
1489
+ total_energy_fan0 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity ;
1490
+ #if HAS_FAN
1491
+ else if (ELAPSED (ms, test_end_ms) && !fan0_done) {
1492
+ set_fan_speed (TERN (SINGLEFAN, 0 , e), 255 );
1493
+ planner.sync_fan_speeds (fan_speed);
1494
+ settle_end_ms = ms + settle_time;
1495
+ test_end_ms = settle_end_ms + test_duration;
1496
+ fan0_done = true ;
1497
+ }
1498
+ else if (ELAPSED (ms, settle_end_ms) && !ELAPSED (ms, test_end_ms))
1499
+ total_energy_fan255 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity ;
1500
+ #endif
1501
+ else if (ELAPSED (ms, test_end_ms)) break ;
1502
+
1503
+ last_temp = current_temp;
1504
+ next_test_ms += MPC_dT * 1000 ;
1505
+ }
1506
+
1507
+ if (!WITHIN (current_temp, t3 - 15 .0f , hotend.target + 15 .0f )) {
1508
+ SERIAL_ECHOLNPGM (STR_MPC_TEMPERATURE_ERROR);
1509
+ TERN_ (DWIN_LCD_PROUI, DWIN_MPCTuning (MPC_TEMP_ERROR));
1510
+ break ;
1511
+ }
1512
+ }
1513
+ wait_for_heatup = false ;
1514
+
1515
+ const float power_fan0 = total_energy_fan0 * 1000 / test_duration;
1516
+ mpc.ambient_xfer_coeff_fan0 = power_fan0 / (hotend.target - ambient_temp);
1517
+
1518
+ #if HAS_FAN
1519
+ const float power_fan255 = total_energy_fan255 * 1000 / test_duration,
1520
+ ambient_xfer_coeff_fan255 = power_fan255 / (hotend.target - ambient_temp);
1521
+ mpc.applyFanAdjustment (ambient_xfer_coeff_fan255);
1522
+ #endif
1523
+
1524
+ // Calculate a new and better asymptotic temperature and re-evaluate the other constants
1525
+ asymp_temp = ambient_temp + mpc.heater_power * (MPC_MAX) / 255 / mpc.ambient_xfer_coeff_fan0 ;
1526
+ block_responsiveness = -log ((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1 ));
1527
+ mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
1528
+ mpc.sensor_responsiveness = block_responsiveness / (1 .0f - (ambient_temp - asymp_temp) * exp (-block_responsiveness * t1_time) / (t1 - asymp_temp));
1529
+
1530
+ SERIAL_ECHOLNPGM (STR_MPC_AUTOTUNE_FINISHED);
1531
+ TERN_ (DWIN_LCD_PROUI, DWIN_MPCTuning (MPC_DONE));
1532
+
1533
+ #if 0
1534
+ SERIAL_ECHOLNPGM("t1_time ", t1_time);
1535
+ SERIAL_ECHOLNPGM("sample_count ", sample_count);
1536
+ SERIAL_ECHOLNPGM("sample_distance ", sample_distance);
1537
+ for (uint8_t i = 0; i < sample_count; i++)
1538
+ SERIAL_ECHOLNPGM("sample ", i, " : ", temp_samples[i]);
1539
+ SERIAL_ECHOLNPGM("t1 ", t1, " t2 ", t2, " t3 ", t3);
1540
+ SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
1541
+ SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
1542
+ #endif
1543
+
1544
+ SERIAL_ECHOLNPGM (" MPC_BLOCK_HEAT_CAPACITY " , mpc.block_heat_capacity );
1545
+ SERIAL_ECHOLNPAIR_F (" MPC_SENSOR_RESPONSIVENESS " , mpc.sensor_responsiveness , 4 );
1546
+ SERIAL_ECHOLNPAIR_F (" MPC_AMBIENT_XFER_COEFF " , mpc.ambient_xfer_coeff_fan0 , 4 );
1547
+ TERN_ (HAS_FAN, SERIAL_ECHOLNPAIR_F (" MPC_AMBIENT_XFER_COEFF_FAN255 " , ambient_xfer_coeff_fan255, 4 ));
1548
+ }
1549
+
1305
1550
#endif // MPC_AUTOTUNE
1306
1551
1307
1552
int16_t Temperature::getHeaterPower (const heater_id_t heater_id) {
0 commit comments