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