Refactor mmu2.cpp and related interfaces
The general idea is to keep platform specific implementation away from the MMU state machines as much as we can. That would enable unit testing the top level MMU state machine and integration into other project as well (if needed).
This commit is contained in:
parent
51374965e4
commit
0555376502
|
|
@ -3,45 +3,43 @@
|
|||
#include "mmu2_error_converter.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_marlin.h"
|
||||
#include "mmu2_marlin_macros.h"
|
||||
#include "mmu2_power.h"
|
||||
#include "mmu2_progress_converter.h"
|
||||
#include "mmu2_reporting.h"
|
||||
|
||||
#include "cardreader.h" // for IS_SD_PRINTING
|
||||
#include "Marlin.h"
|
||||
#include "language.h"
|
||||
#include "messages.h"
|
||||
#include "sound.h"
|
||||
#include "stepper.h"
|
||||
#include "strlen_cx.h"
|
||||
#include "temperature.h"
|
||||
#include "ultralcd.h"
|
||||
#include "SpoolJoin.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
// As of FW 3.12 we only support building the FW with only one extruder, all the multi-extruder infrastructure will be removed.
|
||||
// Saves at least 800B of code size
|
||||
static_assert(EXTRUDERS==1);
|
||||
|
||||
constexpr float MMM_TO_MMS(float MM_M){ return MM_M / 60.0f; }
|
||||
#endif
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
template<typename F>
|
||||
void waitForHotendTargetTemp(uint16_t delay, F f){
|
||||
while (((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)) {
|
||||
while (((thermal_degTargetHotend() - thermal_degHotend()) > 5)) {
|
||||
f();
|
||||
delay_keep_alive(delay);
|
||||
safe_delay_keep_alive(delay);
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForHotendTargetTempBeep(){
|
||||
waitForHotendTargetTemp(3000, []{ });
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardPrompt);
|
||||
MakeSound(Prompt);
|
||||
}
|
||||
|
||||
MMU2 mmu2;
|
||||
|
||||
MMU2::MMU2()
|
||||
: is_mmu_error_monitor_active(false)
|
||||
, logic(&mmu2Serial, MMU2_TOOL_CHANGE_LOAD_LENGTH)
|
||||
, logic(&mmu2Serial, MMU2_TOOL_CHANGE_LOAD_LENGTH, MMU2_LOAD_TO_NOZZLE_FEED_RATE)
|
||||
, extruder(MMU2_NO_TOOL)
|
||||
, tool_change_extruder(MMU2_NO_TOOL)
|
||||
, resume_position()
|
||||
|
|
@ -52,24 +50,15 @@ MMU2::MMU2()
|
|||
, loadFilamentStarted(false)
|
||||
, unloadFilamentStarted(false)
|
||||
, loadingToNozzle(false)
|
||||
, inAutoRetry(false)
|
||||
, retryAttempts(MAX_RETRIES)
|
||||
, toolchange_counter(0)
|
||||
, tmcFailures(0)
|
||||
{
|
||||
}
|
||||
|
||||
void MMU2::Start() {
|
||||
#ifdef MMU_HWRESET
|
||||
WRITE(MMU_RST_PIN, 1);
|
||||
SET_OUTPUT(MMU_RST_PIN); // setup reset pin
|
||||
#endif //MMU_HWRESET
|
||||
|
||||
mmu2Serial.begin(MMU_BAUD);
|
||||
|
||||
PowerOn(); // I repurposed this to serve as our EEPROM disable toggle.
|
||||
Reset(ResetForm::ResetPin);
|
||||
|
||||
mmu2Serial.flush(); // make sure the UART buffer is clear before starting communication
|
||||
|
||||
extruder = MMU2_NO_TOOL;
|
||||
|
|
@ -78,7 +67,7 @@ void MMU2::Start() {
|
|||
// start the communication
|
||||
logic.Start();
|
||||
|
||||
ResetRetryAttempts();
|
||||
logic.ResetRetryAttempts();
|
||||
}
|
||||
|
||||
void MMU2::Stop() {
|
||||
|
|
@ -94,10 +83,17 @@ void MMU2::StopKeepPowered(){
|
|||
|
||||
void MMU2::Reset(ResetForm level){
|
||||
switch (level) {
|
||||
case Software: ResetX0(); break;
|
||||
case ResetPin: TriggerResetPin(); break;
|
||||
case CutThePower: PowerCycle(); break;
|
||||
default: break;
|
||||
case Software:
|
||||
ResetX0();
|
||||
break;
|
||||
case ResetPin:
|
||||
TriggerResetPin();
|
||||
break;
|
||||
case CutThePower:
|
||||
PowerCycle();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +111,7 @@ void MMU2::PowerCycle(){
|
|||
// NOTE: the below will toggle the EEPROM var. Should we
|
||||
// assert this function is never called in the MK3 FW? Do we even care?
|
||||
PowerOff();
|
||||
delay_keep_alive(1000);
|
||||
safe_delay_keep_alive(1000);
|
||||
PowerOn();
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +169,7 @@ void __attribute__((noinline)) MMU2::mmu_loop_inner(bool reportErrors) {
|
|||
if (is_mmu_error_monitor_active) {
|
||||
// Call this every iteration to keep the knob rotation responsive
|
||||
// This includes when mmu_loop is called within manage_response
|
||||
ReportErrorHook((uint16_t)lastErrorCode);
|
||||
ReportErrorHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)lastErrorCode, uint8_t(lastErrorSource));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +189,8 @@ void MMU2::CheckFINDARunout() {
|
|||
|
||||
struct ReportingRAII {
|
||||
CommandInProgress cip;
|
||||
inline ReportingRAII(CommandInProgress cip):cip(cip){
|
||||
explicit inline ReportingRAII(CommandInProgress cip)
|
||||
: cip(cip) {
|
||||
BeginReport(cip, (uint16_t)ProgressCode::EngagingIdler);
|
||||
}
|
||||
inline ~ReportingRAII(){
|
||||
|
|
@ -214,54 +211,40 @@ bool MMU2::WaitForMMUReady(){
|
|||
}
|
||||
|
||||
bool MMU2::RetryIfPossible(uint16_t ec){
|
||||
if( retryAttempts ){
|
||||
if (logic.RetryAttempts()) {
|
||||
SetButtonResponse(ButtonOperations::Retry);
|
||||
// check, that Retry is actually allowed on that operation
|
||||
if( ButtonAvailable(ec) != NoButton ){
|
||||
inAutoRetry = true;
|
||||
logic.SetInAutoRetry(true);
|
||||
SERIAL_ECHOLNPGM("RetryButtonPressed");
|
||||
// We don't decrement until the button is acknowledged by the MMU.
|
||||
//--retryAttempts; // "used" one retry attempt
|
||||
return true;
|
||||
}
|
||||
}
|
||||
inAutoRetry = false;
|
||||
logic.SetInAutoRetry(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void MMU2::ResetRetryAttempts(){
|
||||
SERIAL_ECHOLNPGM("ResetRetryAttempts");
|
||||
retryAttempts = MAX_RETRIES;
|
||||
}
|
||||
|
||||
void MMU2::DecrementRetryAttempts() {
|
||||
if (inAutoRetry && retryAttempts) {
|
||||
SERIAL_ECHOLNPGM("DecrementRetryAttempts");
|
||||
retryAttempts--;
|
||||
}
|
||||
}
|
||||
|
||||
bool MMU2::VerifyFilamentEnteredPTFE()
|
||||
{
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
|
||||
if (!fsensor.getFilamentPresent()) return false;
|
||||
if (WhereIsFilament() == FilamentState::NOT_PRESENT) return false;
|
||||
|
||||
uint8_t fsensorState = 0;
|
||||
// MMU has finished its load, push the filament further by some defined constant length
|
||||
// If the filament sensor reads 0 at any moment, then report FAILURE
|
||||
current_position[E_AXIS] += MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION);
|
||||
plan_buffer_line_curposXYZE(MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
current_position[E_AXIS] -= (MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION));
|
||||
plan_buffer_line_curposXYZE(MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
MoveE(MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION), MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
MoveE( - (MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION)), MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
|
||||
while(blocks_queued())
|
||||
while(planner_any_moves())
|
||||
{
|
||||
// Wait for move to finish and monitor the fsensor the entire time
|
||||
// A single 0 reading will set the bit.
|
||||
fsensorState |= !fsensor.getFilamentPresent();
|
||||
manage_heater();
|
||||
manage_inactivity(true);
|
||||
fsensorState |= (WhereIsFilament() == FilamentState::NOT_PRESENT);
|
||||
marlin_manage_heater();
|
||||
marlin_manage_inactivity(true);
|
||||
}
|
||||
|
||||
if (fsensorState)
|
||||
|
|
@ -278,7 +261,7 @@ bool MMU2::ToolChangeCommonOnce(uint8_t slot){
|
|||
static_assert(MAX_RETRIES > 1); // need >1 retries to do the cut in the last attempt
|
||||
for(uint8_t retries = MAX_RETRIES; retries; --retries){
|
||||
for(;;) {
|
||||
disable_e0(); // it may seem counterintuitive to disable the E-motor, but it gets enabled in the planner whenever the E-motor is to move
|
||||
Disable_E0(); // it may seem counterintuitive to disable the E-motor, but it gets enabled in the planner whenever the E-motor is to move
|
||||
tool_change_extruder = slot;
|
||||
logic.ToolChange(slot); // let the MMU pull the filament out and push a new one in
|
||||
if( manage_response(true, true) )
|
||||
|
|
@ -297,12 +280,12 @@ bool MMU2::ToolChangeCommonOnce(uint8_t slot){
|
|||
// something else is seriously broken and stopping a print is probably our best option.
|
||||
}
|
||||
// reset current position to whatever the planner thinks it is
|
||||
plan_set_e_position(current_position[E_AXIS]);
|
||||
planner_set_current_position_E( planner_get_current_position_E() );
|
||||
if (VerifyFilamentEnteredPTFE()){
|
||||
return true; // success
|
||||
} else { // Prepare a retry attempt
|
||||
unload();
|
||||
if( retries == 2 && eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) == EEPROM_MMU_CUTTER_ENABLED_enabled){
|
||||
if( retries == 2 && cutter_enabled()){
|
||||
cut_filament(slot, false); // try cutting filament tip at the last attempt
|
||||
}
|
||||
}
|
||||
|
|
@ -325,9 +308,6 @@ void MMU2::ToolChangeCommon(uint8_t slot){
|
|||
extruder = slot; //filament change is finished
|
||||
SpoolJoin::spooljoin.setSlot(slot);
|
||||
|
||||
// @@TODO really report onto the serial? May be for the Octoprint? Not important now
|
||||
// SERIAL_ECHO_START();
|
||||
// SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, int(extruder));
|
||||
++toolchange_counter;
|
||||
}
|
||||
|
||||
|
|
@ -338,8 +318,7 @@ bool MMU2::tool_change(uint8_t slot) {
|
|||
if (slot != extruder) {
|
||||
if (/*FindaDetectsFilament()*/
|
||||
/*!IS_SD_PRINTING && !usb_timer.running()*/
|
||||
! printer_active()
|
||||
) {
|
||||
!marlin_printingIsActive()) {
|
||||
// If Tcodes are used manually through the serial
|
||||
// we need to unload manually as well -- but only if FINDA detects filament
|
||||
unload();
|
||||
|
|
@ -347,7 +326,7 @@ bool MMU2::tool_change(uint8_t slot) {
|
|||
|
||||
ReportingRAII rep(CommandInProgress::ToolChange);
|
||||
FSensorBlockRunout blockRunout;
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
ToolChangeCommon(slot);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -371,10 +350,10 @@ bool MMU2::tool_change(char code, uint8_t slot) {
|
|||
} break;
|
||||
|
||||
case 'x': {
|
||||
set_extrude_min_temp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle
|
||||
st_synchronize();
|
||||
thermal_setExtrudeMintemp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle
|
||||
planner_synchronize();
|
||||
ToolChangeCommon(slot); // the only difference was manage_response(false, false), but probably good enough
|
||||
set_extrude_min_temp(EXTRUDE_MINTEMP);
|
||||
thermal_setExtrudeMintemp(EXTRUDE_MINTEMP);
|
||||
} break;
|
||||
|
||||
case 'c': {
|
||||
|
|
@ -398,13 +377,13 @@ uint8_t MMU2::get_tool_change_tool() const {
|
|||
return tool_change_extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : tool_change_extruder;
|
||||
}
|
||||
|
||||
bool MMU2::set_filament_type(uint8_t slot, uint8_t type) {
|
||||
bool MMU2::set_filament_type(uint8_t /*slot*/, uint8_t /*type*/) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
// @@TODO - this is not supported in the new MMU yet
|
||||
slot = slot; // @@TODO
|
||||
type = type; // @@TODO
|
||||
// slot = slot; // @@TODO
|
||||
// type = type; // @@TODO
|
||||
// cmd_arg = filamentType;
|
||||
// command(MMU_CMD_F0 + index);
|
||||
|
||||
|
|
@ -430,14 +409,13 @@ bool MMU2::unload() {
|
|||
// we assume the printer managed to relieve filament tip from the gears,
|
||||
// so repeating that part in case of an MMU restart is not necessary
|
||||
for(;;) {
|
||||
disable_e0();
|
||||
Disable_E0();
|
||||
logic.UnloadFilament();
|
||||
if( manage_response(false, true) )
|
||||
break;
|
||||
IncrementMMUFails();
|
||||
}
|
||||
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
MakeSound(Confirm);
|
||||
|
||||
// no active tool
|
||||
extruder = MMU2_NO_TOOL;
|
||||
|
|
@ -446,20 +424,12 @@ bool MMU2::unload() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void FullScreenMsg(const char *pgmS, uint8_t slot){
|
||||
lcd_update_enable(false);
|
||||
lcd_clear();
|
||||
lcd_puts_at_P(0, 1, pgmS);
|
||||
lcd_print(' ');
|
||||
lcd_print(slot + 1);
|
||||
}
|
||||
|
||||
bool MMU2::cut_filament(uint8_t slot, bool enableFullScreenMsg /* = true */){
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
if( enableFullScreenMsg ){
|
||||
FullScreenMsg(_T(MSG_CUT_FILAMENT), slot);
|
||||
FullScreenMsgCut(slot);
|
||||
}
|
||||
{
|
||||
if( FindaDetectsFilament() ){
|
||||
|
|
@ -468,7 +438,7 @@ bool MMU2::cut_filament(uint8_t slot, bool enableFullScreenMsg /* = true */){
|
|||
|
||||
ReportingRAII rep(CommandInProgress::CutFilament);
|
||||
for(;;){
|
||||
disable_e0();
|
||||
Disable_E0();
|
||||
logic.CutFilament(slot);
|
||||
if( manage_response(false, true) )
|
||||
break;
|
||||
|
|
@ -477,16 +447,16 @@ bool MMU2::cut_filament(uint8_t slot, bool enableFullScreenMsg /* = true */){
|
|||
}
|
||||
extruder = MMU2_NO_TOOL;
|
||||
tool_change_extruder = MMU2_NO_TOOL;
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
MakeSound(SoundType::Confirm);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMU2::loading_test(uint8_t slot){
|
||||
FullScreenMsg(_T(MSG_TESTING_FILAMENT), slot);
|
||||
FullScreenMsgTest(slot);
|
||||
tool_change(slot);
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
unload();
|
||||
lcd_update_enable(true);
|
||||
ScreenUpdateEnable();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -494,20 +464,20 @@ bool MMU2::load_filament(uint8_t slot) {
|
|||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
FullScreenMsg(_T(MSG_LOADING_FILAMENT), slot);
|
||||
FullScreenMsgLoad(slot);
|
||||
|
||||
ReportingRAII rep(CommandInProgress::LoadFilament);
|
||||
for(;;) {
|
||||
disable_e0();
|
||||
Disable_E0();
|
||||
logic.LoadFilament(slot);
|
||||
if( manage_response(false, false) )
|
||||
break;
|
||||
IncrementMMUFails();
|
||||
}
|
||||
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
MakeSound(SoundType::Confirm);
|
||||
|
||||
lcd_update_enable(true);
|
||||
ScreenUpdateEnable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -530,7 +500,7 @@ bool MMU2::load_filament_to_nozzle(uint8_t slot) {
|
|||
|
||||
WaitForHotendTargetTempBeep();
|
||||
|
||||
FullScreenMsg(_T(MSG_LOADING_FILAMENT), slot);
|
||||
FullScreenMsgLoad(slot);
|
||||
{
|
||||
// used for MMU-menu operation "Load to Nozzle"
|
||||
ReportingRAII rep(CommandInProgress::ToolChange);
|
||||
|
|
@ -544,9 +514,9 @@ bool MMU2::load_filament_to_nozzle(uint8_t slot) {
|
|||
|
||||
// Finish loading to the nozzle with finely tuned steps.
|
||||
execute_load_to_nozzle_sequence();
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
MakeSound(Confirm);
|
||||
}
|
||||
lcd_update_enable(true);
|
||||
ScreenUpdateEnable();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -555,7 +525,7 @@ bool MMU2::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) {
|
|||
return false;
|
||||
|
||||
if( enableFullScreenMsg ){
|
||||
FullScreenMsg(_T(MSG_EJECT_FILAMENT), slot);
|
||||
FullScreenMsgEject(slot);
|
||||
}
|
||||
{
|
||||
if( FindaDetectsFilament() ){
|
||||
|
|
@ -564,7 +534,7 @@ bool MMU2::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) {
|
|||
|
||||
ReportingRAII rep(CommandInProgress::EjectFilament);
|
||||
for(;;) {
|
||||
disable_e0();
|
||||
Disable_E0();
|
||||
logic.EjectFilament(slot);
|
||||
if( manage_response(false, true) )
|
||||
break;
|
||||
|
|
@ -573,7 +543,7 @@ bool MMU2::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) {
|
|||
}
|
||||
extruder = MMU2_NO_TOOL;
|
||||
tool_change_extruder = MMU2_NO_TOOL;
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
MakeSound(Confirm);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -590,8 +560,8 @@ void MMU2::SaveHotendTemp(bool turn_off_nozzle) {
|
|||
if (mmu_print_saved & SavedState::Cooldown) return;
|
||||
|
||||
if (turn_off_nozzle && !(mmu_print_saved & SavedState::CooldownPending)){
|
||||
disable_e0();
|
||||
resume_hotend_temp = degTargetHotend(active_extruder);
|
||||
Disable_E0();
|
||||
resume_hotend_temp = thermal_degTargetHotend();
|
||||
mmu_print_saved |= SavedState::CooldownPending;
|
||||
LogEchoEvent_P(PSTR("Heater cooldown pending"));
|
||||
}
|
||||
|
|
@ -600,58 +570,50 @@ void MMU2::SaveHotendTemp(bool turn_off_nozzle) {
|
|||
void MMU2::SaveAndPark(bool move_axes) {
|
||||
if (mmu_print_saved == SavedState::None) { // First occurrence. Save current position, park print head, disable nozzle heater.
|
||||
LogEchoEvent_P(PSTR("Saving and parking"));
|
||||
disable_e0();
|
||||
st_synchronize();
|
||||
Disable_E0();
|
||||
planner_synchronize();
|
||||
|
||||
if (move_axes){
|
||||
mmu_print_saved |= SavedState::ParkExtruder;
|
||||
// save current pos
|
||||
for(uint8_t i = 0; i < 3; ++i){
|
||||
resume_position.xyz[i] = current_position[i];
|
||||
}
|
||||
resume_position = planner_current_position(); // save current pos
|
||||
|
||||
// lift Z
|
||||
raise_z(MMU_ERR_Z_PAUSE_LIFT);
|
||||
|
||||
// move XY aside
|
||||
if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS])
|
||||
{
|
||||
current_position[X_AXIS] = MMU_ERR_X_PAUSE_POS;
|
||||
current_position[Y_AXIS] = MMU_ERR_Y_PAUSE_POS;
|
||||
plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE);
|
||||
st_synchronize();
|
||||
if (all_axes_homed()) {
|
||||
nozzle_park();
|
||||
}
|
||||
}
|
||||
}
|
||||
// keep the motors powered forever (until some other strategy is chosen)
|
||||
// @@TODO do we need that in 8bit?
|
||||
// gcode.reset_stepper_timeout();
|
||||
gcode_reset_stepper_timeout();
|
||||
}
|
||||
|
||||
void MMU2::ResumeHotendTemp() {
|
||||
if ((mmu_print_saved & SavedState::CooldownPending))
|
||||
{
|
||||
if ((mmu_print_saved & SavedState::CooldownPending)) {
|
||||
// Clear the "pending" flag if we haven't cooled yet.
|
||||
mmu_print_saved &= ~(SavedState::CooldownPending);
|
||||
LogEchoEvent_P(PSTR("Cooldown flag cleared"));
|
||||
}
|
||||
if ((mmu_print_saved & SavedState::Cooldown) && resume_hotend_temp) {
|
||||
LogEchoEvent_P(PSTR("Resuming Temp"));
|
||||
MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature "));
|
||||
// @@TODO MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature "));
|
||||
SERIAL_ECHOLN(resume_hotend_temp);
|
||||
mmu_print_saved &= ~(SavedState::Cooldown);
|
||||
setTargetHotend(resume_hotend_temp, active_extruder);
|
||||
lcd_display_message_fullscreen_P(_i("MMU Retry: Restoring temperature...")); ////MSG_MMU_RESTORE_TEMP c=20 r=4
|
||||
thermal_setTargetHotend(resume_hotend_temp);
|
||||
FullScreenMsgRestoringTemperature();
|
||||
//@todo better report the event and let the GUI do its work somewhere else
|
||||
ReportErrorHookSensorLineRender();
|
||||
waitForHotendTargetTemp(100, []{
|
||||
manage_inactivity(true);
|
||||
marlin_manage_inactivity(true);
|
||||
mmu2.mmu_loop_inner(false);
|
||||
ReportErrorHookDynamicRender();
|
||||
});
|
||||
lcd_update_enable(true); // temporary hack to stop this locking the printer...
|
||||
ScreenUpdateEnable(); // temporary hack to stop this locking the printer...
|
||||
LogEchoEvent_P(PSTR("Hotend temperature reached"));
|
||||
lcd_clear();
|
||||
ScreenClear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -659,14 +621,12 @@ void MMU2::ResumeUnpark(){
|
|||
if (mmu_print_saved & SavedState::ParkExtruder) {
|
||||
LogEchoEvent_P(PSTR("Resuming XYZ"));
|
||||
|
||||
current_position[X_AXIS] = resume_position.xyz[X_AXIS];
|
||||
current_position[Y_AXIS] = resume_position.xyz[Y_AXIS];
|
||||
plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE);
|
||||
st_synchronize();
|
||||
// Move XY to starting position, then Z
|
||||
motion_do_blocking_move_to_xy(resume_position.xyz[0], resume_position.xyz[1], feedRate_t(NOZZLE_PARK_XY_FEEDRATE));
|
||||
|
||||
current_position[Z_AXIS] = resume_position.xyz[Z_AXIS];
|
||||
plan_buffer_line_curposXYZE(NOZZLE_PARK_Z_FEEDRATE);
|
||||
st_synchronize();
|
||||
// Move Z_AXIS to saved position
|
||||
motion_do_blocking_move_to_z(resume_position.xyz[2], feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
|
||||
|
||||
mmu_print_saved &= ~(SavedState::ParkExtruder);
|
||||
}
|
||||
}
|
||||
|
|
@ -742,7 +702,7 @@ void MMU2::CheckUserInput(){
|
|||
bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
||||
mmu_print_saved = SavedState::None;
|
||||
|
||||
KEEPALIVE_STATE(IN_PROCESS);
|
||||
MARLIN_KEEPALIVE_STATE_IN_PROCESS;
|
||||
|
||||
LongTimer nozzleTimeout;
|
||||
|
||||
|
|
@ -752,7 +712,7 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
|||
// - still running -> wait normally in idle()
|
||||
// - failed -> then do the safety moves on the printer like before
|
||||
// - finished ok -> proceed with reading other commands
|
||||
delay_keep_alive(0); // calls LogicStep() and remembers its return status
|
||||
safe_delay_keep_alive(0); // calls LogicStep() and remembers its return status
|
||||
|
||||
if (mmu_print_saved & SavedState::CooldownPending){
|
||||
if (!nozzleTimeout.running()){
|
||||
|
|
@ -761,7 +721,7 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
|||
} else if (nozzleTimeout.expired(DEFAULT_SAFETYTIMER_TIME_MINS*60*1000ul)){ // mins->msec.
|
||||
mmu_print_saved &= ~(SavedState::CooldownPending);
|
||||
mmu_print_saved |= SavedState::Cooldown;
|
||||
setAllTargetHotends(0);
|
||||
thermal_setTargetHotend(0);
|
||||
LogEchoEvent_P(PSTR("Heater cooldown"));
|
||||
}
|
||||
} else if (nozzleTimeout.running()) {
|
||||
|
|
@ -775,8 +735,8 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
|||
// the E may have some more moves to finish - wait for them
|
||||
ResumeHotendTemp();
|
||||
ResumeUnpark(); // We can now travel back to the tower or wherever we were when we saved.
|
||||
ResetRetryAttempts(); // Reset the retry counter.
|
||||
st_synchronize();
|
||||
logic.ResetRetryAttempts(); // Reset the retry counter.
|
||||
planner_synchronize();
|
||||
return true;
|
||||
case Interrupted:
|
||||
// now what :D ... big bad ... ramming, unload, retry the whole command originally issued
|
||||
|
|
@ -795,7 +755,7 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
|||
case CommunicationTimeout:
|
||||
case ProtocolError:
|
||||
case ButtonPushed:
|
||||
if (!inAutoRetry){
|
||||
if (! logic.InAutoRetry()){
|
||||
// Don't proceed to the park/save if we are doing an autoretry.
|
||||
SaveAndPark(move_axes);
|
||||
SaveHotendTemp(turn_off_nozzle);
|
||||
|
|
@ -868,25 +828,32 @@ StepStatus MMU2::LogicStep(bool reportErrors) {
|
|||
}
|
||||
|
||||
void MMU2::filament_ramming() {
|
||||
execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step));
|
||||
execute_extruder_sequence(ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step));
|
||||
}
|
||||
|
||||
void MMU2::execute_extruder_sequence(const E_Step *sequence, uint8_t steps) {
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
Enable_E0();
|
||||
|
||||
const E_Step *step = sequence;
|
||||
for (uint8_t i = 0; i < steps; i++) {
|
||||
current_position[E_AXIS] += pgm_read_float(&(step->extrude));
|
||||
plan_buffer_line_curposXYZE(pgm_read_float(&(step->feedRate)));
|
||||
st_synchronize();
|
||||
const float es = pgm_read_float(&(step->extrude));
|
||||
const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate));
|
||||
planner_set_current_position_E(planner_get_current_position_E() + es);
|
||||
planner_line_to_current_position(MMM_TO_MMS(fr_mm_m));
|
||||
|
||||
step++;
|
||||
}
|
||||
planner_synchronize(); // it looks like it's better to sync the moves at the end - smoother move (if the sequence is not too long).
|
||||
|
||||
Disable_E0();
|
||||
}
|
||||
|
||||
void MMU2::execute_load_to_nozzle_sequence() {
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
// Compensate for configurable Extra Loading Distance
|
||||
current_position[E_AXIS] -= (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION);
|
||||
execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0]));
|
||||
planner_set_current_position_E( planner_get_current_position_E() - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION) );
|
||||
execute_extruder_sequence(load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0]));
|
||||
}
|
||||
|
||||
void MMU2::ReportError(ErrorCode ec, ErrorSource res) {
|
||||
|
|
@ -945,7 +912,7 @@ void MMU2::ReportError(ErrorCode ec, ErrorSource res) {
|
|||
// If retry attempts are all used up
|
||||
// or if 'Retry' operation is not available
|
||||
// raise the MMU error sceen and wait for user input
|
||||
ReportErrorHook((uint16_t)ec);
|
||||
ReportErrorHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)ec, uint8_t(lastErrorSource));
|
||||
}
|
||||
|
||||
static_assert(mmu2Magic[0] == 'M'
|
||||
|
|
@ -977,22 +944,21 @@ void MMU2::OnMMUProgressMsgChanged(ProgressCode pc){
|
|||
switch (pc) {
|
||||
case ProgressCode::UnloadingToFinda:
|
||||
if ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::UnloadFilament
|
||||
|| ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange))
|
||||
{
|
||||
|| ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange)) {
|
||||
// If MK3S sent U0 command, ramming sequence takes care of releasing the filament.
|
||||
// If Toolchange is done while printing, PrusaSlicer takes care of releasing the filament
|
||||
// If printing is not in progress, ToolChange will issue a U0 command.
|
||||
break;
|
||||
} else {
|
||||
// We're likely recovering from an MMU error
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
unloadFilamentStarted = true;
|
||||
HelpUnloadToFinda();
|
||||
}
|
||||
break;
|
||||
case ProgressCode::FeedingToFSensor:
|
||||
// prepare for the movement of the E-motor
|
||||
st_synchronize();
|
||||
planner_synchronize();
|
||||
loadFilamentStarted = true;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -1002,17 +968,20 @@ void MMU2::OnMMUProgressMsgChanged(ProgressCode pc){
|
|||
}
|
||||
|
||||
void __attribute__((noinline)) MMU2::HelpUnloadToFinda(){
|
||||
current_position[E_AXIS] -= MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH;
|
||||
plan_buffer_line_curposXYZE(MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE);
|
||||
MoveE(- MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH, MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE);
|
||||
}
|
||||
|
||||
void MMU2::OnMMUProgressMsgSame(ProgressCode pc){
|
||||
switch (pc) {
|
||||
case ProgressCode::UnloadingToFinda:
|
||||
if (unloadFilamentStarted && !blocks_queued()) { // Only plan a move if there is no move ongoing
|
||||
if (fsensor.getFilamentPresent()) {
|
||||
if (unloadFilamentStarted && !planner_any_moves()) { // Only plan a move if there is no move ongoing
|
||||
switch (WhereIsFilament()) {
|
||||
case FilamentState::AT_FSENSOR:
|
||||
case FilamentState::IN_NOZZLE:
|
||||
case FilamentState::UNAVAILABLE: // actually Unavailable makes sense as well to start the E-move to release the filament from the gears
|
||||
HelpUnloadToFinda();
|
||||
} else {
|
||||
break;
|
||||
default:
|
||||
unloadFilamentStarted = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1026,14 +995,12 @@ void MMU2::OnMMUProgressMsgSame(ProgressCode pc){
|
|||
// After the MMU knows the FSENSOR is triggered it will:
|
||||
// 1. Push the filament by additional 30mm (see fsensorToNozzle)
|
||||
// 2. Disengage the idler and push another 2mm.
|
||||
current_position[E_AXIS] += logic.ExtraLoadDistance() + 2;
|
||||
plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
MoveE(logic.ExtraLoadDistance() + 2, MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
break;
|
||||
case FilamentState::NOT_PRESENT:
|
||||
// fsensor not triggered, continue moving extruder
|
||||
if (!blocks_queued()) { // Only plan a move if there is no move ongoing
|
||||
current_position[E_AXIS] += 2.0f;
|
||||
plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
if (!planner_any_moves()) { // Only plan a move if there is no move ongoing
|
||||
MoveE(2.0f, MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -1048,4 +1015,24 @@ void MMU2::OnMMUProgressMsgSame(ProgressCode pc){
|
|||
}
|
||||
}
|
||||
|
||||
//void MMU2::LogErrorEvent(const char *msg) {
|
||||
// MMU2_ERROR_MSG(msg);
|
||||
// SERIAL_ECHOLN();
|
||||
//}
|
||||
|
||||
void MMU2::LogErrorEvent_P(const char *msg_P) {
|
||||
MMU2_ERROR_MSGRPGM(msg_P);
|
||||
SERIAL_ECHOLN();
|
||||
}
|
||||
|
||||
//void MMU2::LogEchoEvent(const char *msg) {
|
||||
// MMU2_ECHO_MSG(msg);
|
||||
// SERIAL_ECHOLN();
|
||||
//}
|
||||
|
||||
void MMU2::LogEchoEvent_P(const char *msg_P) {
|
||||
MMU2_ECHO_MSGRPGM(msg_P);
|
||||
SERIAL_ECHOLN();
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include "mmu2_state.h"
|
||||
#include "mmu2_marlin.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#include "mmu2_protocol_logic.h"
|
||||
typedef float feedRate_t;
|
||||
#else
|
||||
|
||||
#include "protocol_logic.h"
|
||||
#include "../../Marlin/src/core/macros.h"
|
||||
#include "../../Marlin/src/core/types.h"
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
struct E_Step;
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
static constexpr uint8_t MAX_RETRIES = 3U;
|
||||
|
||||
/// @@TODO hmmm, 12 bytes... may be we can reduce that
|
||||
struct xyz_pos_t {
|
||||
float xyz[3];
|
||||
xyz_pos_t()=default;
|
||||
};
|
||||
|
||||
// general MMU setup for MK3
|
||||
enum : uint8_t {
|
||||
FILAMENT_UNKNOWN = 0xffU
|
||||
|
|
@ -36,22 +41,8 @@ public:
|
|||
/// Stops the protocol logic, closes the UART, powers OFF the MMU
|
||||
void Stop();
|
||||
|
||||
/// States of a printer with the MMU:
|
||||
/// - Active
|
||||
/// - Connecting
|
||||
/// - Stopped
|
||||
///
|
||||
/// When the printer's FW starts, the MMU2 mode is either Stopped or NotResponding (based on user's preference).
|
||||
/// When the MMU successfully establishes communication, the state changes to Active.
|
||||
enum class xState : uint_fast8_t {
|
||||
Active, ///< MMU has been detected, connected, communicates and is ready to be worked with.
|
||||
Connecting, ///< MMU is connected but it doesn't communicate (yet). The user wants the MMU, but it is not ready to be worked with.
|
||||
Stopped ///< The user doesn't want the printer to work with the MMU. The MMU itself is not powered and does not work at all.
|
||||
};
|
||||
|
||||
inline xState State() const { return state; }
|
||||
|
||||
// @@TODO temporary wrappers to make old gcc survive the code
|
||||
inline bool Enabled()const { return State() == xState::Active; }
|
||||
|
||||
/// Different levels of resetting the MMU
|
||||
|
|
@ -97,7 +88,6 @@ public:
|
|||
/// @returns true upon success
|
||||
bool WriteRegister(uint8_t address, uint16_t data);
|
||||
|
||||
|
||||
/// The main loop of MMU processing.
|
||||
/// Doesn't loop (block) inside, performs just one step of logic state machines.
|
||||
/// Also, internally it prevents recursive entries.
|
||||
|
|
@ -184,17 +174,13 @@ public:
|
|||
bool is_mmu_error_monitor_active;
|
||||
|
||||
/// Method to read-only mmu_print_saved
|
||||
inline bool MMU_PRINT_SAVED() const { return mmu_print_saved != SavedState::None; }
|
||||
bool MMU_PRINT_SAVED() const { return mmu_print_saved != SavedState::None; }
|
||||
|
||||
/// Automagically "press" a Retry button if we have any retry attempts left
|
||||
/// @param ec ErrorCode enum value
|
||||
/// @returns true if auto-retry is ongoing, false when retry is unavailable or retry attempts are all used up
|
||||
bool RetryIfPossible(uint16_t ec);
|
||||
|
||||
/// Decrement the retry attempts, if in a retry.
|
||||
// Called by the MMU protocol when a sent button is acknowledged.
|
||||
void DecrementRetryAttempts();
|
||||
|
||||
/// @return count for toolchange in current print
|
||||
inline uint16_t ToolChangeCounter() const { return toolchange_counter; };
|
||||
|
||||
|
|
@ -206,8 +192,6 @@ public:
|
|||
inline void ClearTMCFailures() { tmcFailures = 0; }
|
||||
|
||||
private:
|
||||
/// Reset the retryAttempts back to the default value
|
||||
void ResetRetryAttempts();
|
||||
/// Perform software self-reset of the MMU (sends an X0 command)
|
||||
void ResetX0();
|
||||
|
||||
|
|
@ -258,6 +242,20 @@ private:
|
|||
/// Repeated calls when progress code remains the same
|
||||
void OnMMUProgressMsgSame(ProgressCode pc);
|
||||
|
||||
/// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
// void LogErrorEvent(const char *msg);
|
||||
/// Report the msg into the general logging subsystem.
|
||||
/// On the AVR platform this variant reads the input string from PROGMEM.
|
||||
/// On the ARM platform it calls LogErrorEvent directly (silently expecting the compiler to optimize it away)
|
||||
void LogErrorEvent_P(const char *msg_P);
|
||||
|
||||
/// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
// void LogEchoEvent(const char *msg);
|
||||
/// Report the msg into the general logging subsystem.
|
||||
/// On the AVR platform this variant reads the input string from PROGMEM.
|
||||
/// On the ARM platform it calls LogEchoEvent directly (silently expecting the compiler to optimize it away)
|
||||
void LogEchoEvent_P(const char *msg_P);
|
||||
|
||||
/// @brief Save hotend temperature and set flag to cooldown hotend after 60 minutes
|
||||
/// @param turn_off_nozzle if true, the hotend temperature will be set to 0degC after 60 minutes
|
||||
void SaveHotendTemp(bool turn_off_nozzle);
|
||||
|
|
@ -302,7 +300,7 @@ private:
|
|||
uint8_t extruder; ///< currently active slot in the MMU ... somewhat... not sure where to get it from yet
|
||||
uint8_t tool_change_extruder; ///< only used for UI purposes
|
||||
|
||||
xyz_pos_t resume_position;
|
||||
pos3d resume_position;
|
||||
int16_t resume_hotend_temp;
|
||||
|
||||
ProgressCode lastProgressCode = ProgressCode::OK;
|
||||
|
|
@ -322,9 +320,6 @@ private:
|
|||
/// true in case we are doing the LoadToNozzle operation - that means the filament shall be loaded all the way down to the nozzle
|
||||
/// unlike the mid-print ToolChange commands, which only load the first ~30mm and then the G-code takes over.
|
||||
bool loadingToNozzle;
|
||||
|
||||
bool inAutoRetry;
|
||||
uint8_t retryAttempts;
|
||||
uint16_t toolchange_counter;
|
||||
uint16_t tmcFailures;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ namespace MMU2 {
|
|||
enum class FilamentState : uint_fast8_t {
|
||||
NOT_PRESENT = 0, ///< filament sensor doesn't see the filament
|
||||
AT_FSENSOR = 1, ///< filament detected by the filament sensor, but the nozzle has not detected the filament yet
|
||||
IN_NOZZLE = 2 ///< filament detected by the filament sensor and also loaded in the nozzle
|
||||
IN_NOZZLE = 2, ///< filament detected by the filament sensor and also loaded in the nozzle
|
||||
UNAVAILABLE = 3 ///< sensor not available (likely not connected due broken cable)
|
||||
};
|
||||
|
||||
FilamentState WhereIsFilament();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/// @file
|
||||
/// The sole purpose of this interface is to separate Marlin1/Marlin2 from the MMU2 top logic layer.
|
||||
/// Why?
|
||||
/// - unify implementation among MK3 and Buddy FW
|
||||
/// - enable unit testing of MMU2 top layer
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
/// @@TODO hmmm, 12 bytes... may be we can reduce that
|
||||
struct pos3d {
|
||||
float xyz[3];
|
||||
pos3d() = default;
|
||||
inline constexpr pos3d(float x, float y, float z)
|
||||
: xyz { x, y, z } {}
|
||||
pos3d operator=(const float *newP){
|
||||
for(uint8_t i = 0; i < 3; ++i){
|
||||
xyz[i] = newP[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
void MoveE(float delta, float feedRate);
|
||||
|
||||
float raise_z(float delta);
|
||||
|
||||
void planner_synchronize();
|
||||
bool planner_any_moves();
|
||||
float planner_get_machine_position_E_mm();
|
||||
float planner_get_current_position_E();
|
||||
void planner_set_current_position_E(float e);
|
||||
void planner_line_to_current_position(float feedRate_mm_s);
|
||||
pos3d planner_current_position();
|
||||
|
||||
void motion_do_blocking_move_to_xy(float rx, float ry, float feedRate_mm_s);
|
||||
void motion_do_blocking_move_to_z(float z, float feedRate_mm_s);
|
||||
|
||||
void nozzle_park();
|
||||
|
||||
bool marlin_printingIsActive();
|
||||
void marlin_manage_heater();
|
||||
void marlin_manage_inactivity(bool b);
|
||||
void marlin_idle(bool b);
|
||||
|
||||
int16_t thermal_degTargetHotend();
|
||||
int16_t thermal_degHotend();
|
||||
void thermal_setExtrudeMintemp(int16_t t);
|
||||
void thermal_setTargetHotend(int16_t t);
|
||||
|
||||
void safe_delay_keep_alive(uint16_t t);
|
||||
|
||||
void Enable_E0();
|
||||
void Disable_E0();
|
||||
|
||||
bool all_axes_homed();
|
||||
|
||||
void gcode_reset_stepper_timeout();
|
||||
|
||||
bool cutter_enabled();
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/// @file
|
||||
/// MK3 / Marlin1 implementation of support routines for the MMU2
|
||||
#include "Marlin.h"
|
||||
#include "stepper.h"
|
||||
#include "planner.h"
|
||||
#include "mmu2_config.h"
|
||||
#include "temperature.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
void MoveE(float delta, float feedRate) {
|
||||
current_position[E_AXIS] += delta;
|
||||
plan_buffer_line_curposXYZE(feedRate);
|
||||
}
|
||||
|
||||
float raise_z(float delta) {
|
||||
// @@TODO
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
void planner_synchronize() {
|
||||
st_synchronize();
|
||||
}
|
||||
|
||||
bool planner_any_moves() {
|
||||
return blocks_queued();
|
||||
}
|
||||
|
||||
float planner_get_machine_position_E_mm(){
|
||||
// @@TODO return Planner::get_machine_position_mm()[3];
|
||||
}
|
||||
|
||||
float planner_get_current_position_E(){
|
||||
return current_position[E_AXIS];
|
||||
}
|
||||
|
||||
void planner_set_current_position_E(float e){
|
||||
current_position[E_AXIS] = e;
|
||||
}
|
||||
|
||||
void planner_line_to_current_position(float feedRate_mm_s){
|
||||
plan_buffer_line_curposXYZE(feedRate_mm_s);
|
||||
st_synchronize();
|
||||
}
|
||||
|
||||
pos3d planner_current_position(){
|
||||
return pos3d(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
|
||||
}
|
||||
|
||||
void motion_do_blocking_move_to_xy(float rx, float ry, float feedRate_mm_s){
|
||||
current_position[X_AXIS] = rx;
|
||||
current_position[Y_AXIS] = ry;
|
||||
plan_buffer_line_curposXYZE(feedRate_mm_s);
|
||||
st_synchronize();
|
||||
}
|
||||
|
||||
void motion_do_blocking_move_to_z(float z, float feedRate_mm_s){
|
||||
current_position[Z_AXIS] = z;
|
||||
plan_buffer_line_curposXYZE(feedRate_mm_s);
|
||||
st_synchronize();
|
||||
}
|
||||
|
||||
void nozzle_park() {
|
||||
current_position[X_AXIS] = MMU_ERR_X_PAUSE_POS;
|
||||
current_position[Y_AXIS] = MMU_ERR_Y_PAUSE_POS;
|
||||
planner_line_to_current_position(NOZZLE_PARK_XY_FEEDRATE);
|
||||
st_synchronize();
|
||||
}
|
||||
|
||||
bool marlin_printingIsActive() {
|
||||
// return IS_SD_PRINTING || usb_timer_running();
|
||||
return printer_active();
|
||||
}
|
||||
|
||||
void marlin_manage_heater(){
|
||||
manage_heater();
|
||||
}
|
||||
|
||||
void marlin_manage_inactivity(bool b){
|
||||
manage_inactivity(b);
|
||||
}
|
||||
|
||||
void marlin_idle(bool b){
|
||||
manage_heater();
|
||||
manage_inactivity(true);
|
||||
}
|
||||
|
||||
int16_t thermal_degTargetHotend() {
|
||||
return degTargetHotend(0);
|
||||
}
|
||||
|
||||
int16_t thermal_degHotend() {
|
||||
return degHotend(0);
|
||||
}
|
||||
|
||||
void thermal_setExtrudeMintemp(int16_t t) {
|
||||
set_extrude_min_temp(t);
|
||||
}
|
||||
|
||||
void thermal_setTargetHotend(int16_t t) {
|
||||
setTargetHotend(t, 0);
|
||||
}
|
||||
|
||||
void safe_delay_keep_alive(uint16_t t) {
|
||||
delay_keep_alive(t);
|
||||
}
|
||||
|
||||
void gcode_reset_stepper_timeout(){
|
||||
// empty
|
||||
}
|
||||
|
||||
void Enable_E0(){ enable_e0(); }
|
||||
void Disable_E0(){ disable_e0(); }
|
||||
|
||||
bool all_axes_homed(){
|
||||
return axis_known_position[X_AXIS] && axis_known_position[Y_AXIS];
|
||||
}
|
||||
|
||||
bool cutter_enabled(){
|
||||
return eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) == EEPROM_MMU_CUTTER_ENABLED_enabled;
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/// @file
|
||||
/// This file will not be the same on Marlin1 and Marlin2.
|
||||
/// Its purpose is to unify different macros in either of Marlin incarnations.
|
||||
#pragma once
|
||||
|
||||
#ifdef __AVR__
|
||||
#include "Marlin.h"
|
||||
// brings _O and _T macros into MMU
|
||||
#include "language.h"
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS)
|
||||
#else
|
||||
#include "../../gcode/gcode.h"
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS)
|
||||
#endif
|
||||
|
|
@ -10,13 +10,18 @@ namespace MMU2 {
|
|||
|
||||
// sadly, on MK3 we cannot do actual power cycle on HW...
|
||||
// so we just block the MMU via EEPROM var instead.
|
||||
void power_on()
|
||||
{
|
||||
void power_on() {
|
||||
#ifdef MMU_HWRESET
|
||||
WRITE(MMU_RST_PIN, 1);
|
||||
SET_OUTPUT(MMU_RST_PIN); // setup reset pin
|
||||
#endif //MMU_HWRESET
|
||||
|
||||
eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, true);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void power_off()
|
||||
{
|
||||
void power_off() {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
#include "mmu2_protocol_logic.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
// on MK3/S/+ we shuffle the timers a bit, thus "_millis" may not equal "millis"
|
||||
#include "system_timer.h"
|
||||
#else
|
||||
// irrelevant on Buddy FW, just keep "_millis" as "millis"
|
||||
#include <wiring_time.h>
|
||||
#define _millis millis
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
|
@ -20,7 +29,8 @@ const uint8_t ProtocolLogic::regs16Addrs[ProtocolLogic::regs16Count] PROGMEM = {
|
|||
};
|
||||
|
||||
const uint8_t ProtocolLogic::initRegs8Addrs[ProtocolLogic::initRegs8Count] PROGMEM = {
|
||||
0x0b, // extra load distance
|
||||
0x0b, // extra load distance [mm]
|
||||
0x14, // pulley slow feedrate [mm/s]
|
||||
};
|
||||
|
||||
void ProtocolLogic::CheckAndReportAsyncEvents() {
|
||||
|
|
@ -112,9 +122,12 @@ void ProtocolLogic::SendWriteRegister(uint8_t index, uint16_t value, ScopeState
|
|||
// searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW)
|
||||
struct OldMMUFWDetector {
|
||||
uint8_t ok;
|
||||
inline constexpr OldMMUFWDetector():ok(0) { }
|
||||
inline constexpr OldMMUFWDetector()
|
||||
: ok(0) {}
|
||||
|
||||
enum class State : uint8_t { MatchingPart, SomethingElse, Matched };
|
||||
enum class State : uint8_t { MatchingPart,
|
||||
SomethingElse,
|
||||
Matched };
|
||||
|
||||
/// @returns true when "ok\n" gets detected
|
||||
State Detect(uint8_t c){
|
||||
|
|
@ -144,6 +157,7 @@ StepStatus ProtocolLogic::ExpectingMessage() {
|
|||
case DecodeStatus::MessageCompleted:
|
||||
rsp = protocol.GetResponseMsg();
|
||||
LogResponse();
|
||||
// @@TODO reset direction of communication
|
||||
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||
return MessageReady;
|
||||
case DecodeStatus::NeedMoreData:
|
||||
|
|
@ -386,7 +400,7 @@ StepStatus ProtocolLogic::CommandStep() {
|
|||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// Button was accepted, decrement the retry.
|
||||
mmu2.DecrementRetryAttempts();
|
||||
DecrementRetryAttempts();
|
||||
}
|
||||
SendAndUpdateFilamentSensor();
|
||||
break;
|
||||
|
|
@ -467,7 +481,7 @@ StepStatus ProtocolLogic::IdleStep() {
|
|||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// Button was accepted, decrement the retry.
|
||||
mmu2.DecrementRetryAttempts();
|
||||
DecrementRetryAttempts();
|
||||
}
|
||||
StartReading8bitRegisters();
|
||||
return Processing;
|
||||
|
|
@ -495,7 +509,7 @@ StepStatus ProtocolLogic::IdleStep() {
|
|||
return Finished;
|
||||
}
|
||||
|
||||
ProtocolLogic::ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance)
|
||||
ProtocolLogic::ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate)
|
||||
: explicitPrinterError(ErrorCode::OK)
|
||||
, currentScope(Scope::Stopped)
|
||||
, scopeState(ScopeState::Ready)
|
||||
|
|
@ -510,12 +524,16 @@ ProtocolLogic::ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance)
|
|||
, progressCode(ProgressCode::OK)
|
||||
, buttonCode(NoButton)
|
||||
, lastFSensor((uint8_t)WhereIsFilament())
|
||||
, regs8 { 0, 0, 0 }
|
||||
, regs16 { 0, 0 }
|
||||
, initRegs8 { extraLoadDistance }
|
||||
, regIndex(0)
|
||||
, mmuFwVersion { 0, 0, 0 }
|
||||
{}
|
||||
, retryAttempts(MAX_RETRIES)
|
||||
, inAutoRetry(false)
|
||||
{
|
||||
// @@TODO currently, I don't see a way of writing the initialization better :(
|
||||
// I'd like to write something like: initRegs8 { extraLoadDistance, pulleySlowFeedrate }
|
||||
// avr-gcc seems to like such a syntax, ARM gcc doesn't
|
||||
initRegs8[0] = extraLoadDistance;
|
||||
initRegs8[1] = pulleySlowFeedrate;
|
||||
}
|
||||
|
||||
void ProtocolLogic::Start() {
|
||||
state = State::InitSequence;
|
||||
|
|
@ -811,6 +829,18 @@ uint8_t ProtocolLogic::CommandInProgress() const {
|
|||
return (uint8_t)ReqMsg().code;
|
||||
}
|
||||
|
||||
void ProtocolLogic::DecrementRetryAttempts() {
|
||||
if (inAutoRetry && retryAttempts) {
|
||||
SERIAL_ECHOLNPGM("DecrementRetryAttempts");
|
||||
retryAttempts--;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolLogic::ResetRetryAttempts() {
|
||||
SERIAL_ECHOLNPGM("ResetRetryAttempts");
|
||||
retryAttempts = MAX_RETRIES;
|
||||
}
|
||||
|
||||
bool DropOutFilter::Record(StepStatus ss) {
|
||||
if (occurrences == maxOccurrences) {
|
||||
cause = ss;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <avr/pgmspace.h>
|
||||
// #include <array> //@@TODO Don't we have STL for AVR somewhere?
|
||||
|
||||
#ifdef __AVR__
|
||||
#include "mmu2/error_codes.h"
|
||||
#include "mmu2/progress_codes.h"
|
||||
#include "mmu2/buttons.h"
|
||||
#include "mmu2_protocol.h"
|
||||
|
||||
// #include <array> std array is not available on AVR ... we need to "fake" it
|
||||
namespace std {
|
||||
template<typename T, uint8_t N>
|
||||
class array {
|
||||
T data[N];
|
||||
|
|
@ -14,17 +22,26 @@ public:
|
|||
return data[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
#else
|
||||
|
||||
#include "mmu2/error_codes.h"
|
||||
#include "mmu2/progress_codes.h"
|
||||
#include "mmu2/buttons.h"
|
||||
#include "mmu2_protocol.h"
|
||||
#include <array>
|
||||
#include "../../../../../../Prusa-Firmware-MMU/src/logic/error_codes.h"
|
||||
#include "../../../../../../Prusa-Firmware-MMU/src/logic/progress_codes.h"
|
||||
|
||||
// prevent ARM HAL macros from breaking our code
|
||||
#undef CRC
|
||||
#include "../../../../../../Prusa-Firmware-MMU/src/modules/protocol.h"
|
||||
#include "buttons.h"
|
||||
#endif
|
||||
|
||||
#include "mmu2_serial.h"
|
||||
|
||||
/// New MMU2 protocol logic
|
||||
namespace MMU2 {
|
||||
|
||||
static constexpr uint8_t MAX_RETRIES = 3U;
|
||||
|
||||
using namespace modules::protocol;
|
||||
|
||||
class ProtocolLogic;
|
||||
|
|
@ -73,7 +90,7 @@ public:
|
|||
/// Logic layer of the MMU vs. printer communication protocol
|
||||
class ProtocolLogic {
|
||||
public:
|
||||
ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance);
|
||||
ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate);
|
||||
|
||||
/// Start/Enable communication with the MMU
|
||||
void Start();
|
||||
|
|
@ -143,6 +160,21 @@ public:
|
|||
return mmuFwVersion[2];
|
||||
}
|
||||
|
||||
/// Current number of retry attempts left
|
||||
constexpr uint8_t RetryAttempts() const { return retryAttempts; }
|
||||
|
||||
/// Decrement the retry attempts, if in a retry.
|
||||
/// Called by the MMU protocol when a sent button is acknowledged.
|
||||
void DecrementRetryAttempts();
|
||||
|
||||
/// Reset the retryAttempts back to the default value
|
||||
void ResetRetryAttempts();
|
||||
|
||||
constexpr bool InAutoRetry() const { return inAutoRetry; }
|
||||
void SetInAutoRetry(bool iar) {
|
||||
inAutoRetry = iar;
|
||||
}
|
||||
|
||||
inline void SetPrinterError(ErrorCode ec){
|
||||
explicitPrinterError = ec;
|
||||
}
|
||||
|
|
@ -315,7 +347,7 @@ private:
|
|||
|
||||
Protocol protocol; ///< protocol codec
|
||||
|
||||
array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
|
||||
std::array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
|
||||
uint8_t lrb;
|
||||
|
||||
MMU2Serial *uart; ///< UART interface
|
||||
|
|
@ -330,31 +362,27 @@ private:
|
|||
static constexpr uint8_t regs8Count = 3;
|
||||
static_assert(regs8Count > 0); // code is not ready for empty lists of registers
|
||||
static const uint8_t regs8Addrs[regs8Count] PROGMEM;
|
||||
uint8_t regs8[regs8Count];
|
||||
uint8_t regs8[regs8Count] = { 0, 0, 0 };
|
||||
|
||||
// 16bit registers
|
||||
static constexpr uint8_t regs16Count = 2;
|
||||
static_assert(regs16Count > 0); // code is not ready for empty lists of registers
|
||||
static const uint8_t regs16Addrs[regs16Count] PROGMEM;
|
||||
uint16_t regs16[regs16Count];
|
||||
uint16_t regs16[regs16Count] = { 0, 0 };
|
||||
|
||||
// 8bit init values to be sent to the MMU after line up
|
||||
static constexpr uint8_t initRegs8Count = 1;
|
||||
static constexpr uint8_t initRegs8Count = 2;
|
||||
static_assert(initRegs8Count > 0); // code is not ready for empty lists of registers
|
||||
static const uint8_t initRegs8Addrs[initRegs8Count] PROGMEM;
|
||||
uint8_t initRegs8[initRegs8Count];
|
||||
|
||||
uint8_t regIndex;
|
||||
|
||||
uint8_t mmuFwVersion[3];
|
||||
uint8_t mmuFwVersion[3] = { 0, 0, 0 };
|
||||
uint16_t mmuFwVersionBuild;
|
||||
|
||||
friend class ProtocolLogicPartBase;
|
||||
friend class Stopped;
|
||||
friend class Command;
|
||||
friend class Idle;
|
||||
friend class StartSeq;
|
||||
friend class DelayedRestart;
|
||||
uint8_t retryAttempts;
|
||||
bool inAutoRetry;
|
||||
|
||||
friend class MMU2;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ enum class ReportErrorHookStates : uint8_t {
|
|||
|
||||
enum ReportErrorHookStates ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
|
||||
void ReportErrorHook(uint16_t ec) {
|
||||
void ReportErrorHook(CommandInProgress /*cip*/, uint16_t ec, uint8_t /*es*/) {
|
||||
if (mmu2.MMUCurrentErrorCode() == ErrorCode::OK && mmu2.MMULastErrorSource() == MMU2::ErrorSourceMMU) {
|
||||
// If the error code suddenly changes to OK, that means
|
||||
// a button was pushed on the MMU and the LCD should
|
||||
|
|
@ -292,4 +292,44 @@ void IncrementMMUFails(){
|
|||
eeprom_increment_word((uint16_t *)EEPROM_MMU_FAIL_TOT);
|
||||
}
|
||||
|
||||
void MakeSound(SoundType s){
|
||||
Sound_MakeSound( (eSOUND_TYPE)s);
|
||||
}
|
||||
|
||||
static void FullScreenMsg(const char *pgmS, uint8_t slot){
|
||||
lcd_update_enable(false);
|
||||
lcd_clear();
|
||||
lcd_puts_at_P(0, 1, pgmS);
|
||||
lcd_print(' ');
|
||||
lcd_print(slot + 1);
|
||||
}
|
||||
|
||||
void FullScreenMsgCut(uint8_t slot){
|
||||
FullScreenMsg(_T(MSG_CUT_FILAMENT), slot);
|
||||
}
|
||||
|
||||
void FullScreenMsgEject(uint8_t slot){
|
||||
FullScreenMsg(_T(MSG_EJECT_FILAMENT), slot);
|
||||
}
|
||||
|
||||
void FullScreenMsgTest(uint8_t slot){
|
||||
FullScreenMsg(_T(MSG_TESTING_FILAMENT), slot);
|
||||
}
|
||||
|
||||
void FullScreenMsgLoad(uint8_t slot){
|
||||
FullScreenMsg(_T(MSG_LOADING_FILAMENT), slot);
|
||||
}
|
||||
|
||||
void FullScreenMsgRestoringTemperature(){
|
||||
lcd_display_message_fullscreen_P(_i("MMU Retry: Restoring temperature...")); ////MSG_MMU_RESTORE_TEMP c=20 r=4
|
||||
}
|
||||
|
||||
void ScreenUpdateEnable(){
|
||||
lcd_update_enable(true);
|
||||
}
|
||||
|
||||
void ScreenClear(){
|
||||
lcd_clear();
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
|
|||
|
|
@ -22,13 +22,12 @@ void BeginReport(CommandInProgress cip, uint16_t ec);
|
|||
/// Called at the end of every MMU operation
|
||||
void EndReport(CommandInProgress cip, uint16_t ec);
|
||||
|
||||
/**
|
||||
* @brief Called when the MMU or MK3S sends operation error (even repeatedly).
|
||||
* Render MMU error screen on the LCD. This must be non-blocking
|
||||
* and allow the MMU and printer to communicate with each other.
|
||||
* @param[in] ec error code
|
||||
*/
|
||||
void ReportErrorHook(uint16_t ec);
|
||||
/// @brief Called when the MMU or MK3S sends operation error (even repeatedly).
|
||||
/// Render MMU error screen on the LCD. This must be non-blocking
|
||||
/// and allow the MMU and printer to communicate with each other.
|
||||
/// @param[in] ec error code
|
||||
/// @param[in] es error source
|
||||
void ReportErrorHook(CommandInProgress cip, uint16_t ec, uint8_t es);
|
||||
|
||||
/// Called when the MMU sends operation progress update
|
||||
void ReportProgressHook(CommandInProgress cip, uint16_t ec);
|
||||
|
|
@ -53,4 +52,21 @@ void IncrementLoadFails();
|
|||
/// Increments EEPROM cell - number of MMU errors
|
||||
void IncrementMMUFails();
|
||||
|
||||
// Beware: enum values intentionally chosen to match the 8bit FW to save code size
|
||||
enum SoundType {
|
||||
Prompt = 2,
|
||||
Confirm = 3
|
||||
};
|
||||
|
||||
void MakeSound(SoundType s);
|
||||
|
||||
void FullScreenMsgCut(uint8_t slot);
|
||||
void FullScreenMsgEject(uint8_t slot);
|
||||
void FullScreenMsgTest(uint8_t slot);
|
||||
void FullScreenMsgLoad(uint8_t slot);
|
||||
void FullScreenMsgRestoringTemperature();
|
||||
|
||||
void ScreenUpdateEnable();
|
||||
void ScreenClear();
|
||||
|
||||
} // namespace
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @file mmu_state.h
|
||||
* @brief status of mmu
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
namespace MMU2 {
|
||||
/// States of a printer with the MMU:
|
||||
/// - Active
|
||||
/// - Connecting
|
||||
/// - Stopped
|
||||
///
|
||||
/// When the printer's FW starts, the MMU2 mode is either Stopped or NotResponding (based on user's preference).
|
||||
/// When the MMU successfully establishes communication, the state changes to Active.
|
||||
enum class xState : uint_fast8_t {
|
||||
Active, ///< MMU has been detected, connected, communicates and is ready to be worked with.
|
||||
Connecting, ///< MMU is connected but it doesn't communicate (yet). The user wants the MMU, but it is not ready to be worked with.
|
||||
Stopped ///< The user doesn't want the printer to work with the MMU. The MMU itself is not powered and does not work at all.
|
||||
};
|
||||
|
||||
} // namespace MMU2
|
||||
Loading…
Reference in New Issue