MMU2 interface overhaul
First port of the new MMU2-printer interface into 8bit FW.
This commit is contained in:
parent
c27e4623c5
commit
2e293e90a0
|
|
@ -461,7 +461,7 @@ void gcode_M114();
|
||||||
#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
|
#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
|
||||||
void gcode_M123();
|
void gcode_M123();
|
||||||
#endif //FANCHECK and TACH_0 and TACH_1
|
#endif //FANCHECK and TACH_0 and TACH_1
|
||||||
void gcode_M701();
|
void gcode_M701(uint8_t mmuSlotIndex);
|
||||||
|
|
||||||
#define UVLO !(PINE & (1<<4))
|
#define UVLO !(PINE & (1<<4))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
#include "Tcodes.h"
|
||||||
#include "Dcodes.h"
|
#include "Dcodes.h"
|
||||||
#include "AutoDeplete.h"
|
#include "AutoDeplete.h"
|
||||||
|
|
||||||
|
|
@ -125,7 +126,7 @@
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "mmu.h"
|
#include "mmu2.h"
|
||||||
|
|
||||||
#define VERSION_STRING "1.0.2"
|
#define VERSION_STRING "1.0.2"
|
||||||
|
|
||||||
|
|
@ -1047,7 +1048,7 @@ void setup()
|
||||||
{
|
{
|
||||||
timer2_init(); // enables functional millis
|
timer2_init(); // enables functional millis
|
||||||
|
|
||||||
mmu_init();
|
MMU2::mmu2.Start();
|
||||||
|
|
||||||
ultralcd_init();
|
ultralcd_init();
|
||||||
|
|
||||||
|
|
@ -1623,7 +1624,7 @@ void setup()
|
||||||
#endif //UVLO_SUPPORT
|
#endif //UVLO_SUPPORT
|
||||||
|
|
||||||
fCheckModeInit();
|
fCheckModeInit();
|
||||||
fSetMmuMode(mmu_enabled);
|
fSetMmuMode(MMU2::mmu2.Enabled());
|
||||||
KEEPALIVE_STATE(NOT_BUSY);
|
KEEPALIVE_STATE(NOT_BUSY);
|
||||||
#ifdef WATCHDOG
|
#ifdef WATCHDOG
|
||||||
wdt_enable(WDTO_4S);
|
wdt_enable(WDTO_4S);
|
||||||
|
|
@ -1856,7 +1857,7 @@ void loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif //TMC2130
|
#endif //TMC2130
|
||||||
mmu_loop();
|
MMU2::mmu2.mmu_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DEFINE_PGM_READ_ANY(type, reader) \
|
#define DEFINE_PGM_READ_ANY(type, reader) \
|
||||||
|
|
@ -3469,15 +3470,14 @@ static T gcode_M600_filament_change_z_shift()
|
||||||
#else
|
#else
|
||||||
return T(0);
|
return T(0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gcode_M600(bool automatic, float x_position, float y_position, float z_shift, float e_shift, float /*e_shift_late*/)
|
static void gcode_M600(bool automatic, float x_position, float y_position, float z_shift, float e_shift, float /*e_shift_late*/) {
|
||||||
{
|
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
float lastpos[4];
|
float lastpos[4];
|
||||||
|
|
||||||
prusa_statistics(22);
|
prusa_statistics(22);
|
||||||
|
|
||||||
//First backup current position and settings
|
//First backup current position and settings
|
||||||
int feedmultiplyBckp = feedmultiply;
|
int feedmultiplyBckp = feedmultiply;
|
||||||
float HotendTempBckp = degTargetHotend(active_extruder);
|
float HotendTempBckp = degTargetHotend(active_extruder);
|
||||||
|
|
@ -3488,33 +3488,35 @@ static void gcode_M600(bool automatic, float x_position, float y_position, float
|
||||||
lastpos[Z_AXIS] = current_position[Z_AXIS];
|
lastpos[Z_AXIS] = current_position[Z_AXIS];
|
||||||
lastpos[E_AXIS] = current_position[E_AXIS];
|
lastpos[E_AXIS] = current_position[E_AXIS];
|
||||||
|
|
||||||
//Retract E
|
// Retract E
|
||||||
current_position[E_AXIS] += e_shift;
|
current_position[E_AXIS] += e_shift;
|
||||||
plan_buffer_line_curposXYZE(FILAMENTCHANGE_RFEED);
|
plan_buffer_line_curposXYZE(FILAMENTCHANGE_RFEED);
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
|
|
||||||
//Lift Z
|
// Lift Z
|
||||||
current_position[Z_AXIS] += z_shift;
|
current_position[Z_AXIS] += z_shift;
|
||||||
clamp_to_software_endstops(current_position);
|
clamp_to_software_endstops(current_position);
|
||||||
plan_buffer_line_curposXYZE(FILAMENTCHANGE_ZFEED);
|
plan_buffer_line_curposXYZE(FILAMENTCHANGE_ZFEED);
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
|
|
||||||
//Move XY to side
|
// Move XY to side
|
||||||
current_position[X_AXIS] = x_position;
|
current_position[X_AXIS] = x_position;
|
||||||
current_position[Y_AXIS] = y_position;
|
current_position[Y_AXIS] = y_position;
|
||||||
plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED);
|
plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED);
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
|
|
||||||
//Beep, manage nozzle heater and wait for user to start unload filament
|
// Beep, manage nozzle heater and wait for user to start unload filament
|
||||||
if(!mmu_enabled) M600_wait_for_user(HotendTempBckp);
|
if (!MMU2::mmu2.Enabled())
|
||||||
|
M600_wait_for_user(HotendTempBckp);
|
||||||
|
|
||||||
lcd_change_fil_state = 0;
|
lcd_change_fil_state = 0;
|
||||||
|
|
||||||
// Unload filament
|
// Unload filament
|
||||||
if (mmu_enabled) extr_unload(); //unload just current filament for multimaterial printers (used also in M702)
|
if (MMU2::mmu2.Enabled())
|
||||||
else unload_filament(true); //unload filament for single material (used also in M702)
|
MMU2::mmu2.unload(); // unload just current filament for multimaterial printers (used also in M702)
|
||||||
//finish moves
|
else
|
||||||
st_synchronize();
|
unload_filament(true); // unload filament for single material (used also in M702)
|
||||||
|
st_synchronize(); // finish moves
|
||||||
|
|
||||||
#ifdef FILAMENT_SENSOR
|
#ifdef FILAMENT_SENSOR
|
||||||
fsensor.setRunoutEnabled(false); //suppress filament runouts while loading filament.
|
fsensor.setRunoutEnabled(false); //suppress filament runouts while loading filament.
|
||||||
|
|
@ -3524,14 +3526,11 @@ static void gcode_M600(bool automatic, float x_position, float y_position, float
|
||||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!mmu_enabled)
|
if (!MMU2::mmu2.Enabled()) {
|
||||||
{
|
|
||||||
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
||||||
lcd_change_fil_state = lcd_show_fullscreen_message_yes_no_and_wait_P(
|
lcd_change_fil_state =
|
||||||
_i("Was filament unload successful?"), ////MSG_UNLOAD_SUCCESSFUL c=20 r=2
|
lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Was filament unload successful?"), false, true); ////MSG_UNLOAD_SUCCESSFUL c=20 r=2
|
||||||
false, true);
|
if (lcd_change_fil_state == 0) {
|
||||||
if (lcd_change_fil_state == 0)
|
|
||||||
{
|
|
||||||
lcd_clear();
|
lcd_clear();
|
||||||
lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
|
lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
|
||||||
current_position[X_AXIS] -= 100;
|
current_position[X_AXIS] -= 100;
|
||||||
|
|
@ -3541,55 +3540,53 @@ static void gcode_M600(bool automatic, float x_position, float y_position, float
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled()) {
|
||||||
{
|
|
||||||
if (!automatic) {
|
if (!automatic) {
|
||||||
if (saved_printing) mmu_eject_filament(mmu_extruder, false); //if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it
|
if (saved_printing)
|
||||||
mmu_M600_wait_and_beep();
|
MMU2::mmu2.eject_filament(MMU2::mmu2.get_current_tool(),
|
||||||
|
false); // if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it
|
||||||
|
//@@TODO mmu_M600_wait_and_beep();
|
||||||
if (saved_printing) {
|
if (saved_printing) {
|
||||||
|
|
||||||
lcd_clear();
|
lcd_clear();
|
||||||
lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
|
lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
|
||||||
|
|
||||||
mmu_command(MmuCmd::R0);
|
//@@TODO mmu_command(MmuCmd::R0);
|
||||||
manage_response(false, false);
|
// manage_response(false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mmu_M600_load_filament(automatic, HotendTempBckp);
|
//@@TODO mmu_M600_load_filament(automatic, HotendTempBckp);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
M600_load_filament();
|
M600_load_filament();
|
||||||
|
|
||||||
if (!automatic) M600_check_state(HotendTempBckp);
|
if (!automatic)
|
||||||
|
M600_check_state(HotendTempBckp);
|
||||||
|
|
||||||
lcd_update_enable(true);
|
lcd_update_enable(true);
|
||||||
|
|
||||||
//Not let's go back to print
|
// Not let's go back to print
|
||||||
fanSpeed = fanSpeedBckp;
|
fanSpeed = fanSpeedBckp;
|
||||||
|
|
||||||
//Feed a little of filament to stabilize pressure
|
// Feed a little of filament to stabilize pressure
|
||||||
if (!automatic)
|
if (!automatic) {
|
||||||
{
|
|
||||||
current_position[E_AXIS] += FILAMENTCHANGE_RECFEED;
|
current_position[E_AXIS] += FILAMENTCHANGE_RECFEED;
|
||||||
plan_buffer_line_curposXYZE(FILAMENTCHANGE_EXFEED);
|
plan_buffer_line_curposXYZE(FILAMENTCHANGE_EXFEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Move XY back
|
// Move XY back
|
||||||
plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS],
|
plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], FILAMENTCHANGE_XYFEED, active_extruder);
|
||||||
FILAMENTCHANGE_XYFEED, active_extruder);
|
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
//Move Z back
|
// Move Z back
|
||||||
plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], current_position[E_AXIS],
|
plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], current_position[E_AXIS], FILAMENTCHANGE_ZFEED, active_extruder);
|
||||||
FILAMENTCHANGE_ZFEED, active_extruder);
|
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
|
|
||||||
//Set E position to original
|
// Set E position to original
|
||||||
plan_set_e_position(lastpos[E_AXIS]);
|
plan_set_e_position(lastpos[E_AXIS]);
|
||||||
|
|
||||||
memcpy(current_position, lastpos, sizeof(lastpos));
|
memcpy(current_position, lastpos, sizeof(lastpos));
|
||||||
set_destination_to_current();
|
set_destination_to_current();
|
||||||
|
|
||||||
//Recover feed rate
|
// Recover feed rate
|
||||||
feedmultiply = feedmultiplyBckp;
|
feedmultiply = feedmultiplyBckp;
|
||||||
char cmd[9];
|
char cmd[9];
|
||||||
sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp);
|
sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp);
|
||||||
|
|
@ -3603,33 +3600,26 @@ static void gcode_M600(bool automatic, float x_position, float y_position, float
|
||||||
custom_message_type = CustomMsg::Status;
|
custom_message_type = CustomMsg::Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gcode_M701()
|
void gcode_M701(uint8_t mmuSlotIndex){
|
||||||
{
|
printf_P(PSTR("gcode_M701 begin\n"));
|
||||||
printf_P(PSTR("gcode_M701 begin\n"));
|
|
||||||
|
|
||||||
#ifdef FILAMENT_SENSOR
|
#ifdef FILAMENT_SENSOR
|
||||||
fsensor.setRunoutEnabled(false); //suppress filament runouts while loading filament.
|
fsensor.setRunoutEnabled(false); // suppress filament runouts while loading filament.
|
||||||
fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament.
|
fsensor.setAutoLoadEnabled(false); // suppress filament autoloads while loading filament.
|
||||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||||
fsensor.setJamDetectionEnabled(false); //suppress filament jam detection while loading filament.
|
fsensor.setJamDetectionEnabled(false); // suppress filament jam detection while loading filament.
|
||||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
prusa_statistics(22);
|
prusa_statistics(22);
|
||||||
|
|
||||||
if (mmu_enabled)
|
|
||||||
{
|
|
||||||
extr_adj(tmp_extruder);//loads current extruder
|
|
||||||
mmu_extruder = tmp_extruder;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
enable_z();
|
|
||||||
custom_message_type = CustomMsg::FilamentLoading;
|
|
||||||
|
|
||||||
#ifdef FSENSOR_QUALITY
|
if (MMU2::mmu2.Enabled() && mmuSlotIndex < MMU_FILAMENT_COUNT) {
|
||||||
fsensor_oq_meassure_start(40);
|
MMU2::mmu2.load_filament(mmuSlotIndex); // loads current extruder
|
||||||
#endif //FSENSOR_QUALITY
|
// mmu_extruder = mmuSlotIndex; // @@TODO shall load filament set current tool to some specific index? We don't do that anymore.
|
||||||
|
} else {
|
||||||
|
enable_z();
|
||||||
|
custom_message_type = CustomMsg::FilamentLoading;
|
||||||
|
|
||||||
const int feed_mm_before_raising = 30;
|
const int feed_mm_before_raising = 30;
|
||||||
static_assert(feed_mm_before_raising <= FILAMENTCHANGE_FIRSTFEED);
|
static_assert(feed_mm_before_raising <= FILAMENTCHANGE_FIRSTFEED);
|
||||||
|
|
@ -3639,41 +3629,30 @@ void gcode_M701()
|
||||||
plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); //fast sequence
|
plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); //fast sequence
|
||||||
st_synchronize();
|
st_synchronize();
|
||||||
|
|
||||||
raise_z_above(MIN_Z_FOR_LOAD, false);
|
raise_z_above(MIN_Z_FOR_LOAD, false);
|
||||||
current_position[E_AXIS] += feed_mm_before_raising;
|
current_position[E_AXIS] += feed_mm_before_raising;
|
||||||
plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); //fast sequence
|
plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); //fast sequence
|
||||||
|
|
||||||
load_filament_final_feed(); //slow sequence
|
|
||||||
st_synchronize();
|
|
||||||
|
|
||||||
Sound_MakeCustom(50,500,false);
|
load_filament_final_feed(); // slow sequence
|
||||||
|
st_synchronize();
|
||||||
|
|
||||||
if (!farm_mode && loading_flag) {
|
Sound_MakeCustom(50, 500, false);
|
||||||
lcd_load_filament_color_check();
|
|
||||||
}
|
|
||||||
lcd_update_enable(true);
|
|
||||||
lcd_update(2);
|
|
||||||
lcd_setstatuspgm(MSG_WELCOME);
|
|
||||||
disable_z();
|
|
||||||
loading_flag = false;
|
|
||||||
custom_message_type = CustomMsg::Status;
|
|
||||||
|
|
||||||
#ifdef FSENSOR_QUALITY
|
if (!farm_mode && loading_flag) {
|
||||||
fsensor_oq_meassure_stop();
|
lcd_load_filament_color_check();
|
||||||
|
}
|
||||||
|
lcd_update_enable(true);
|
||||||
|
lcd_update(2);
|
||||||
|
lcd_setstatuspgm(MSG_WELCOME);
|
||||||
|
disable_z();
|
||||||
|
loading_flag = false;
|
||||||
|
custom_message_type = CustomMsg::Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
eFilamentAction = FilamentAction::None;
|
||||||
|
|
||||||
if (!fsensor_oq_result())
|
|
||||||
{
|
|
||||||
bool disable = lcd_show_fullscreen_message_yes_no_and_wait_P(_n("Fil. sensor response is poor, disable it?"), false, true);
|
|
||||||
lcd_update_enable(true);
|
|
||||||
lcd_update(2);
|
|
||||||
if (disable)
|
|
||||||
fsensor_disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
eFilamentAction = FilamentAction::None;
|
|
||||||
|
|
||||||
#ifdef FILAMENT_SENSOR
|
#ifdef FILAMENT_SENSOR
|
||||||
fsensor.settings_init(); //restore filament runout state.
|
fsensor.settings_init(); // restore filament runout state.
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -4263,7 +4242,7 @@ void process_commands()
|
||||||
}
|
}
|
||||||
else if (code_seen_P(PSTR("MMURES"))) // PRUSA MMURES
|
else if (code_seen_P(PSTR("MMURES"))) // PRUSA MMURES
|
||||||
{
|
{
|
||||||
mmu_reset();
|
MMU2::mmu2.Reset(MMU2::MMU2::Software);
|
||||||
}
|
}
|
||||||
else if (code_seen_P(PSTR("RESET"))) { // PRUSA RESET
|
else if (code_seen_P(PSTR("RESET"))) { // PRUSA RESET
|
||||||
#ifdef WATCHDOG
|
#ifdef WATCHDOG
|
||||||
|
|
@ -7614,14 +7593,13 @@ Sigma_Exit:
|
||||||
{
|
{
|
||||||
// currently three different materials are needed (default, flex and PVA)
|
// currently three different materials are needed (default, flex and PVA)
|
||||||
// add storing this information for different load/unload profiles etc. in the future
|
// add storing this information for different load/unload profiles etc. in the future
|
||||||
// firmware does not wait for "ok" from mmu
|
if (MMU2::mmu2.Enabled())
|
||||||
if (mmu_enabled)
|
|
||||||
{
|
{
|
||||||
uint8_t extruder = 255;
|
uint8_t extruder = 255;
|
||||||
uint8_t filament = FILAMENT_UNDEFINED;
|
uint8_t filament = FILAMENT_UNDEFINED;
|
||||||
if(code_seen('E')) extruder = code_value_uint8();
|
if(code_seen('E')) extruder = code_value_uint8();
|
||||||
if(code_seen('F')) filament = code_value_uint8();
|
if(code_seen('F')) filament = code_value_uint8();
|
||||||
mmu_set_filament_type(extruder, filament);
|
MMU2::mmu2.set_filament_type(extruder, filament);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -7859,7 +7837,7 @@ Sigma_Exit:
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mmu_enabled && code_seen_P(PSTR("AUTO")))
|
if (MMU2::mmu2.Enabled() && code_seen_P(PSTR("AUTO")))
|
||||||
automatic = true;
|
automatic = true;
|
||||||
|
|
||||||
gcode_M600(automatic, x_position, y_position, z_shift, e_shift_init, e_shift_late);
|
gcode_M600(automatic, x_position, y_position, z_shift, e_shift_init, e_shift_late);
|
||||||
|
|
@ -8509,9 +8487,10 @@ Sigma_Exit:
|
||||||
*/
|
*/
|
||||||
case 701:
|
case 701:
|
||||||
{
|
{
|
||||||
if (mmu_enabled && (code_seen('E') || code_seen('T')))
|
uint8_t mmuSlotIndex = 0xffU;
|
||||||
tmp_extruder = code_value_uint8();
|
if (MMU2::mmu2.Enabled() && code_seen('E'))
|
||||||
gcode_M701();
|
mmuSlotIndex = code_value_uint8();
|
||||||
|
gcode_M701(mmuSlotIndex);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -8528,10 +8507,10 @@ Sigma_Exit:
|
||||||
case 702:
|
case 702:
|
||||||
{
|
{
|
||||||
if (code_seen('C')) {
|
if (code_seen('C')) {
|
||||||
if(mmu_enabled) extr_unload(); //! if "C" unload current filament; if mmu is not present no action is performed
|
if(MMU2::mmu2.Enabled()) MMU2::mmu2.unload(); //! if "C" unload current filament; if mmu is not present no action is performed
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(mmu_enabled) extr_unload(); //! unload current filament
|
if(MMU2::mmu2.Enabled()) MMU2::mmu2.unload(); //! unload current filament
|
||||||
else unload_filament();
|
else unload_filament();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8558,139 +8537,8 @@ Sigma_Exit:
|
||||||
@n Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load.
|
@n Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load.
|
||||||
@n Tc Load to nozzle after filament was prepared by Tc and extruder nozzle is already heated.
|
@n Tc Load to nozzle after filament was prepared by Tc and extruder nozzle is already heated.
|
||||||
*/
|
*/
|
||||||
else if(code_seen('T'))
|
else if(code_seen('T')){
|
||||||
{
|
TCodes(strchr_pointer, code_value());
|
||||||
static const char duplicate_Tcode_ignored[] PROGMEM = "Duplicate T-code ignored.";
|
|
||||||
|
|
||||||
int index;
|
|
||||||
bool load_to_nozzle = false;
|
|
||||||
for (index = 1; *(strchr_pointer + index) == ' ' || *(strchr_pointer + index) == '\t'; index++);
|
|
||||||
|
|
||||||
*(strchr_pointer + index) = tolower(*(strchr_pointer + index));
|
|
||||||
|
|
||||||
if ((*(strchr_pointer + index) < '0' || *(strchr_pointer + index) > '4') && *(strchr_pointer + index) != '?' && *(strchr_pointer + index) != 'x' && *(strchr_pointer + index) != 'c') {
|
|
||||||
SERIAL_ECHOLNPGM("Invalid T code.");
|
|
||||||
}
|
|
||||||
else if (*(strchr_pointer + index) == 'x'){ //load to bondtech gears; if mmu is not present do nothing
|
|
||||||
if (mmu_enabled)
|
|
||||||
{
|
|
||||||
tmp_extruder = choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT));
|
|
||||||
if ((tmp_extruder == mmu_extruder) && mmu_fil_loaded) //dont execute the same T-code twice in a row
|
|
||||||
{
|
|
||||||
puts_P(duplicate_Tcode_ignored);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
st_synchronize();
|
|
||||||
mmu_command(MmuCmd::T0 + tmp_extruder);
|
|
||||||
manage_response(true, true, MMU_TCODE_MOVE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (*(strchr_pointer + index) == 'c') { //load to from bondtech gears to nozzle (nozzle should be preheated)
|
|
||||||
if (mmu_enabled)
|
|
||||||
{
|
|
||||||
st_synchronize();
|
|
||||||
mmu_continue_loading(usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal));
|
|
||||||
mmu_extruder = tmp_extruder; //filament change is finished
|
|
||||||
mmu_load_to_nozzle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (*(strchr_pointer + index) == '?')
|
|
||||||
{
|
|
||||||
if(mmu_enabled)
|
|
||||||
{
|
|
||||||
tmp_extruder = choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT));
|
|
||||||
load_to_nozzle = true;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
tmp_extruder = choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tmp_extruder = code_value();
|
|
||||||
if (mmu_enabled && lcd_autoDepleteEnabled())
|
|
||||||
{
|
|
||||||
tmp_extruder = ad_getAlternative(tmp_extruder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
st_synchronize();
|
|
||||||
|
|
||||||
if (mmu_enabled)
|
|
||||||
{
|
|
||||||
if ((tmp_extruder == mmu_extruder) && mmu_fil_loaded) //dont execute the same T-code twice in a row
|
|
||||||
{
|
|
||||||
puts_P(duplicate_Tcode_ignored);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
|
|
||||||
if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED))
|
|
||||||
{
|
|
||||||
mmu_command(MmuCmd::K0 + tmp_extruder);
|
|
||||||
manage_response(true, true, MMU_UNLOAD_MOVE);
|
|
||||||
}
|
|
||||||
#endif //defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
|
|
||||||
mmu_command(MmuCmd::T0 + tmp_extruder);
|
|
||||||
manage_response(true, true, MMU_TCODE_MOVE);
|
|
||||||
mmu_continue_loading(usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal));
|
|
||||||
|
|
||||||
mmu_extruder = tmp_extruder; //filament change is finished
|
|
||||||
|
|
||||||
if (load_to_nozzle)// for single material usage with mmu
|
|
||||||
{
|
|
||||||
mmu_load_to_nozzle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (tmp_extruder >= EXTRUDERS) {
|
|
||||||
SERIAL_ECHO_START;
|
|
||||||
SERIAL_ECHO('T');
|
|
||||||
SERIAL_PROTOCOLLN((int)tmp_extruder);
|
|
||||||
SERIAL_ECHOLNRPGM(_n("Invalid extruder"));////MSG_INVALID_EXTRUDER
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
#if EXTRUDERS > 1
|
|
||||||
bool make_move = false;
|
|
||||||
#endif
|
|
||||||
if (code_seen('F')) {
|
|
||||||
#if EXTRUDERS > 1
|
|
||||||
make_move = true;
|
|
||||||
#endif
|
|
||||||
next_feedrate = code_value();
|
|
||||||
if (next_feedrate > 0.0) {
|
|
||||||
feedrate = next_feedrate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if EXTRUDERS > 1
|
|
||||||
if (tmp_extruder != active_extruder) {
|
|
||||||
// Save current position to return to after applying extruder offset
|
|
||||||
set_destination_to_current();
|
|
||||||
// Offset extruder (only by XY)
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < 2; i++) {
|
|
||||||
current_position[i] = current_position[i] -
|
|
||||||
extruder_offset[i][active_extruder] +
|
|
||||||
extruder_offset[i][tmp_extruder];
|
|
||||||
}
|
|
||||||
// Set the new active extruder and position
|
|
||||||
active_extruder = tmp_extruder;
|
|
||||||
plan_set_position_curposXYZE();
|
|
||||||
// Move to the old position if 'F' was in the parameters
|
|
||||||
if (make_move) {
|
|
||||||
prepare_move();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
SERIAL_ECHO_START;
|
|
||||||
SERIAL_ECHORPGM(_n("Active Extruder: "));////MSG_ACTIVE_EXTRUDER
|
|
||||||
SERIAL_PROTOCOLLN((int)active_extruder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // end if(code_seen('T')) (end of T codes)
|
} // end if(code_seen('T')) (end of T codes)
|
||||||
/*!
|
/*!
|
||||||
#### End of T-Codes
|
#### End of T-Codes
|
||||||
|
|
@ -9491,7 +9339,7 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) //default argument s
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
check_axes_activity();
|
check_axes_activity();
|
||||||
mmu_loop();
|
MMU2::mmu2.mmu_loop();
|
||||||
|
|
||||||
// handle longpress
|
// handle longpress
|
||||||
if(lcd_longpress_trigger)
|
if(lcd_longpress_trigger)
|
||||||
|
|
@ -11422,10 +11270,12 @@ void M600_check_state(float nozzle_temp)
|
||||||
{
|
{
|
||||||
// Filament failed to load so load it again
|
// Filament failed to load so load it again
|
||||||
case 2:
|
case 2:
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled()){
|
||||||
mmu_M600_load_filament(false, nozzle_temp); //nonautomatic load; change to "wrong filament loaded" option?
|
//@@TODO mmu_M600_load_filament(false, nozzle_temp); //nonautomatic load; change to "wrong filament loaded" option?
|
||||||
else
|
|
||||||
|
} else {
|
||||||
M600_load_filament_movements();
|
M600_load_filament_movements();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Filament loaded properly but color is not clear
|
// Filament loaded properly but color is not clear
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
#include "Tcodes.h"
|
||||||
|
#include "Marlin.h"
|
||||||
|
#include "mmu2.h"
|
||||||
|
#include "stepper.h"
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "language.h"
|
||||||
|
#include "messages.h"
|
||||||
|
#include "ultralcd.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char duplicate_Tcode_ignored[] PROGMEM = "Duplicate T-code ignored.";
|
||||||
|
|
||||||
|
inline bool IsInvalidTCode(char *const s, uint8_t i) {
|
||||||
|
return ((s[i] < '0' || s[i] > '4') && s[i] != '?' && s[i] != 'x' && s[i] != 'c');
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void TCodeInvalid() {
|
||||||
|
SERIAL_ECHOLNPGM("Invalid T code.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// load to bondtech gears; if mmu is not present do nothing
|
||||||
|
void TCodeX() {
|
||||||
|
if (MMU2::mmu2.Enabled()) {
|
||||||
|
uint8_t selectedSlot = choose_menu_P(_T(MSG_CHOOSE_FILAMENT), _T(MSG_FILAMENT));
|
||||||
|
if ((selectedSlot == MMU2::mmu2.get_current_tool()) /*&& mmu_fil_loaded @@TODO */){
|
||||||
|
// dont execute the same T-code twice in a row
|
||||||
|
puts_P(duplicate_Tcode_ignored);
|
||||||
|
} else {
|
||||||
|
st_synchronize();
|
||||||
|
MMU2::mmu2.tool_change(selectedSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load to from bondtech gears to nozzle (nozzle should be preheated)
|
||||||
|
void TCodeC() {
|
||||||
|
if (MMU2::mmu2.Enabled()) {
|
||||||
|
st_synchronize();
|
||||||
|
// @@TODO mmu_continue_loading(usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal));
|
||||||
|
// mmu_extruder = selectedSlot; // filament change is finished
|
||||||
|
// MMU2::mmu2.load_filament_to_nozzle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SChooseFromMenu {
|
||||||
|
uint8_t slot:7;
|
||||||
|
uint8_t loadToNozzle:1;
|
||||||
|
inline constexpr SChooseFromMenu(uint8_t slot, bool loadToNozzle):slot(slot), loadToNozzle(loadToNozzle){}
|
||||||
|
inline constexpr SChooseFromMenu():slot(0), loadToNozzle(false) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
SChooseFromMenu TCodeChooseFromMenu() {
|
||||||
|
if (MMU2::mmu2.Enabled()) {
|
||||||
|
return SChooseFromMenu( choose_menu_P(_T(MSG_CHOOSE_FILAMENT), _T(MSG_FILAMENT)), true );
|
||||||
|
} else {
|
||||||
|
return SChooseFromMenu( choose_menu_P(_T(MSG_CHOOSE_EXTRUDER), _T(MSG_EXTRUDER)), false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCodes(char *const strchr_pointer, uint8_t codeValue) {
|
||||||
|
uint8_t index;
|
||||||
|
for (index = 1; strchr_pointer[index] == ' ' || strchr_pointer[index] == '\t'; index++)
|
||||||
|
;
|
||||||
|
|
||||||
|
strchr_pointer[index] = tolower(strchr_pointer[index]);
|
||||||
|
|
||||||
|
if (IsInvalidTCode(strchr_pointer, index))
|
||||||
|
TCodeInvalid();
|
||||||
|
else if (strchr_pointer[index] == 'x')
|
||||||
|
TCodeX();
|
||||||
|
else if (strchr_pointer[index] == 'c')
|
||||||
|
TCodeC();
|
||||||
|
else {
|
||||||
|
SChooseFromMenu selectedSlot;
|
||||||
|
if (strchr_pointer[index] == '?')
|
||||||
|
selectedSlot = TCodeChooseFromMenu();
|
||||||
|
else {
|
||||||
|
selectedSlot.slot = codeValue;
|
||||||
|
if (MMU2::mmu2.Enabled() && lcd_autoDepleteEnabled()) {
|
||||||
|
// @@TODO selectedSlot.slot = ad_getAlternative(selectedSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st_synchronize();
|
||||||
|
|
||||||
|
if (MMU2::mmu2.Enabled()) {
|
||||||
|
if ((selectedSlot.slot == MMU2::mmu2.get_current_tool()) /*&& mmu_fil_loaded @@TODO*/){
|
||||||
|
// don't execute the same T-code twice in a row
|
||||||
|
puts_P(duplicate_Tcode_ignored);
|
||||||
|
} else {
|
||||||
|
#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
|
||||||
|
if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t *)EEPROM_MMU_CUTTER_ENABLED)) {
|
||||||
|
mmu_command(MmuCmd::K0 + selectedSlot);
|
||||||
|
manage_response(true, true, MMU_UNLOAD_MOVE);
|
||||||
|
}
|
||||||
|
#endif // defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
|
||||||
|
MMU2::mmu2.tool_change(selectedSlot.slot);
|
||||||
|
// @@TODO mmu_continue_loading(usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal));
|
||||||
|
|
||||||
|
if (selectedSlot.loadToNozzle){ // for single material usage with mmu
|
||||||
|
MMU2::mmu2.load_filament_to_nozzle(selectedSlot.slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedSlot.slot >= EXTRUDERS) {
|
||||||
|
SERIAL_ECHO_START;
|
||||||
|
SERIAL_ECHO('T');
|
||||||
|
SERIAL_ECHOLN(selectedSlot.slot + '0');
|
||||||
|
SERIAL_ECHOLNRPGM(_n("Invalid extruder")); ////MSG_INVALID_EXTRUDER
|
||||||
|
} else {
|
||||||
|
// @@TODO if (code_seen('F')) {
|
||||||
|
// next_feedrate = code_value();
|
||||||
|
// if (next_feedrate > 0.0) {
|
||||||
|
// feedrate = next_feedrate;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
SERIAL_ECHO_START;
|
||||||
|
SERIAL_ECHORPGM(_n("Active Extruder: ")); ////MSG_ACTIVE_EXTRUDER
|
||||||
|
SERIAL_ECHOLN(active_extruder + '0'); // this is not changed in our FW at all, can be optimized away
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
/// @file
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void TCodes(char * const strchr_pointer, uint8_t codeValue);
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#include "language.h"
|
#include "language.h"
|
||||||
#include "Marlin.h"
|
#include "Marlin.h"
|
||||||
#include "cmdqueue.h"
|
#include "cmdqueue.h"
|
||||||
#include "mmu.h"
|
#include "mmu2.h"
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
//! @brief Wait for preheat
|
//! @brief Wait for preheat
|
||||||
|
|
@ -34,7 +34,7 @@ void lay1cal_wait_preheat()
|
||||||
//! @param filament filament to use (applies for MMU only)
|
//! @param filament filament to use (applies for MMU only)
|
||||||
void lay1cal_load_filament(char *cmd_buffer, uint8_t filament)
|
void lay1cal_load_filament(char *cmd_buffer, uint8_t filament)
|
||||||
{
|
{
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
enquecommand_P(PSTR("M83"));
|
enquecommand_P(PSTR("M83"));
|
||||||
enquecommand_P(PSTR("G1 Y-3.0 F1000.0"));
|
enquecommand_P(PSTR("G1 Y-3.0 F1000.0"));
|
||||||
|
|
@ -73,7 +73,7 @@ void lay1cal_intro_line()
|
||||||
cmd_intro_mmu_12,
|
cmd_intro_mmu_12,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
for (uint8_t i = 0; i < (sizeof(intro_mmu_cmd)/sizeof(intro_mmu_cmd[0])); ++i)
|
for (uint8_t i = 0; i < (sizeof(intro_mmu_cmd)/sizeof(intro_mmu_cmd[0])); ++i)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
120
Firmware/mmu.h
120
Firmware/mmu.h
|
|
@ -1,120 +0,0 @@
|
||||||
//! @file
|
|
||||||
|
|
||||||
#ifndef MMU_H
|
|
||||||
#define MMU_H
|
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include "Timer.h"
|
|
||||||
|
|
||||||
|
|
||||||
extern bool mmu_enabled;
|
|
||||||
extern bool mmu_fil_loaded;
|
|
||||||
|
|
||||||
extern uint8_t mmu_extruder;
|
|
||||||
|
|
||||||
extern uint8_t tmp_extruder;
|
|
||||||
|
|
||||||
extern int8_t mmu_finda;
|
|
||||||
extern LongTimer mmu_last_finda_response;
|
|
||||||
extern bool ir_sensor_detected;
|
|
||||||
|
|
||||||
extern int16_t mmu_version;
|
|
||||||
extern int16_t mmu_buildnr;
|
|
||||||
|
|
||||||
extern uint16_t mmu_power_failures;
|
|
||||||
|
|
||||||
#define MMU_FILAMENT_UNKNOWN 255
|
|
||||||
|
|
||||||
#define MMU_NO_MOVE 0
|
|
||||||
#define MMU_UNLOAD_MOVE 1
|
|
||||||
#define MMU_LOAD_MOVE 2
|
|
||||||
#define MMU_TCODE_MOVE 3
|
|
||||||
|
|
||||||
#define MMU_LOAD_FEEDRATE 19.02f //mm/s
|
|
||||||
#define MMU_LOAD_TIME_MS 2000 //should be fine tuned to load time for shortest allowed PTFE tubing and maximum loading speed
|
|
||||||
|
|
||||||
enum class MmuCmd : uint_least8_t
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
T0,
|
|
||||||
T1,
|
|
||||||
T2,
|
|
||||||
T3,
|
|
||||||
T4,
|
|
||||||
L0,
|
|
||||||
L1,
|
|
||||||
L2,
|
|
||||||
L3,
|
|
||||||
L4,
|
|
||||||
C0,
|
|
||||||
U0,
|
|
||||||
E0,
|
|
||||||
E1,
|
|
||||||
E2,
|
|
||||||
E3,
|
|
||||||
E4,
|
|
||||||
K0,
|
|
||||||
K1,
|
|
||||||
K2,
|
|
||||||
K3,
|
|
||||||
K4,
|
|
||||||
R0,
|
|
||||||
S3,
|
|
||||||
W0, //!< Wait and signal load error
|
|
||||||
};
|
|
||||||
|
|
||||||
inline MmuCmd operator+ (MmuCmd cmd, uint8_t filament)
|
|
||||||
{
|
|
||||||
return static_cast<MmuCmd>(static_cast<uint8_t>(cmd) + filament );
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t operator- (MmuCmd cmda, MmuCmd cmdb)
|
|
||||||
{
|
|
||||||
return (static_cast<uint8_t>(cmda) - static_cast<uint8_t>(cmdb));
|
|
||||||
}
|
|
||||||
|
|
||||||
extern int mmu_puts_P(const char* str);
|
|
||||||
|
|
||||||
extern int mmu_printf_P(const char* format, ...);
|
|
||||||
|
|
||||||
extern int8_t mmu_rx_ok(void);
|
|
||||||
|
|
||||||
extern bool check_for_ir_sensor();
|
|
||||||
|
|
||||||
extern void mmu_init(void);
|
|
||||||
|
|
||||||
extern void mmu_loop(void);
|
|
||||||
|
|
||||||
|
|
||||||
extern void mmu_reset(void);
|
|
||||||
|
|
||||||
extern int8_t mmu_set_filament_type(uint8_t extruder, uint8_t filament);
|
|
||||||
|
|
||||||
extern void mmu_command(MmuCmd cmd);
|
|
||||||
|
|
||||||
extern bool mmu_get_response(uint8_t move = 0);
|
|
||||||
|
|
||||||
extern void manage_response(bool move_axes, bool turn_off_nozzle, uint8_t move = MMU_NO_MOVE);
|
|
||||||
|
|
||||||
extern void mmu_load_to_nozzle();
|
|
||||||
|
|
||||||
extern void mmu_M600_load_filament(bool automatic, float nozzle_temp);
|
|
||||||
extern void mmu_M600_wait_and_beep();
|
|
||||||
|
|
||||||
extern void extr_adj(uint8_t extruder);
|
|
||||||
extern void extr_unload();
|
|
||||||
extern void load_all();
|
|
||||||
|
|
||||||
extern bool mmu_check_version();
|
|
||||||
extern void mmu_show_warning();
|
|
||||||
extern void lcd_mmu_load_to_nozzle(uint8_t filament_nr);
|
|
||||||
extern void mmu_eject_filament(uint8_t filament, bool recover);
|
|
||||||
#ifdef MMU_HAS_CUTTER
|
|
||||||
extern void mmu_cut_filament(uint8_t filament_nr);
|
|
||||||
#endif //MMU_HAS_CUTTER
|
|
||||||
extern void mmu_continue_loading(bool blocking);
|
|
||||||
extern void mmu_filament_ramming();
|
|
||||||
extern void mmu_wait_for_heater_blocking();
|
|
||||||
extern void mmu_load_step(bool synchronize = true);
|
|
||||||
|
|
||||||
#endif //MMU_H
|
|
||||||
|
|
@ -0,0 +1,711 @@
|
||||||
|
#include "mmu2.h"
|
||||||
|
#include "mmu2_fsensor.h"
|
||||||
|
#include "mmu2_log.h"
|
||||||
|
#include "mmu2_power.h"
|
||||||
|
#include "mmu2_reporting.h"
|
||||||
|
|
||||||
|
#include "Marlin.h"
|
||||||
|
#include "stepper.h"
|
||||||
|
#include "mmu2_error_converter.h"
|
||||||
|
#include "mmu2_progress_converter.h"
|
||||||
|
|
||||||
|
// @@TODO remove this and enable it in the configuration files
|
||||||
|
// Settings for filament load / unload from the LCD menu.
|
||||||
|
// This is for Prusa MK3-style extruders. Customize for your hardware.
|
||||||
|
#define MMU2_FILAMENTCHANGE_EJECT_FEED 80.0
|
||||||
|
#define MMU2_LOAD_TO_NOZZLE_SEQUENCE \
|
||||||
|
{ 7.2, 562 }, \
|
||||||
|
{ 14.4, 871 }, \
|
||||||
|
{ 36.0, 1393 }, \
|
||||||
|
{ 14.4, 871 }, \
|
||||||
|
{ 50.0, 198 }
|
||||||
|
|
||||||
|
// @@TODO
|
||||||
|
#define FILAMENT_MMU2_RAMMING_SEQUENCE { 7.2, 562 }
|
||||||
|
|
||||||
|
//@@TODO extract into configuration if it makes sense
|
||||||
|
|
||||||
|
// Nominal distance from the extruder gear to the nozzle tip is 87mm
|
||||||
|
// However, some slipping may occur and we need separate distances for
|
||||||
|
// LoadToNozzle and ToolChange.
|
||||||
|
// - +5mm seemed good for LoadToNozzle,
|
||||||
|
// - but too much (made blobs) for a ToolChange
|
||||||
|
static constexpr float MMU2_LOAD_TO_NOZZLE_LENGTH = 87.0F + 5.0F;
|
||||||
|
|
||||||
|
// As discussed with our PrusaSlicer profile specialist
|
||||||
|
// - ToolChange shall not try to push filament into the very tip of the nozzle
|
||||||
|
// to have some space for additional G-code to tune the extruded filament length
|
||||||
|
// in the profile
|
||||||
|
static constexpr float MMU2_TOOL_CHANGE_LOAD_LENGTH = 30.0F;
|
||||||
|
|
||||||
|
static constexpr float MMU2_LOAD_TO_NOZZLE_FEED_RATE = 20.0F;
|
||||||
|
static constexpr uint8_t MMU2_NO_TOOL = 99;
|
||||||
|
static constexpr uint32_t MMU_BAUD = 115200;
|
||||||
|
|
||||||
|
typedef uint16_t feedRate_t;
|
||||||
|
|
||||||
|
struct E_Step {
|
||||||
|
float extrude; ///< extrude distance in mm
|
||||||
|
feedRate_t feedRate; ///< feed rate in mm/s
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr E_Step ramming_sequence[] PROGMEM = FILAMENT_MMU2_RAMMING_SEQUENCE;
|
||||||
|
static constexpr E_Step load_to_nozzle_sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE };
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
void execute_extruder_sequence(const E_Step *sequence, int steps);
|
||||||
|
|
||||||
|
MMU2 mmu2;
|
||||||
|
|
||||||
|
MMU2::MMU2()
|
||||||
|
: logic(&mmu2Serial)
|
||||||
|
, extruder(MMU2_NO_TOOL)
|
||||||
|
, resume_position()
|
||||||
|
, resume_hotend_temp(0)
|
||||||
|
, logicStepLastStatus(StepStatus::Finished)
|
||||||
|
, state(xState::Stopped)
|
||||||
|
, mmu_print_saved(false)
|
||||||
|
, loadFilamentStarted(false)
|
||||||
|
, loadingToNozzle(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::Start() {
|
||||||
|
mmu2Serial.begin(MMU_BAUD);
|
||||||
|
|
||||||
|
PowerOn();
|
||||||
|
mmu2Serial.flush(); // make sure the UART buffer is clear before starting communication
|
||||||
|
|
||||||
|
extruder = MMU2_NO_TOOL;
|
||||||
|
state = xState::Connecting;
|
||||||
|
|
||||||
|
// start the communication
|
||||||
|
logic.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::Stop() {
|
||||||
|
StopKeepPowered();
|
||||||
|
PowerOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::StopKeepPowered(){
|
||||||
|
state = xState::Stopped;
|
||||||
|
logic.Stop();
|
||||||
|
mmu2Serial.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::Reset(ResetForm level){
|
||||||
|
switch (level) {
|
||||||
|
case Software: ResetX0(); break;
|
||||||
|
case ResetPin: TriggerResetPin(); break;
|
||||||
|
case CutThePower: PowerCycle(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::ResetX0() {
|
||||||
|
logic.ResetMMU(); // Send soft reset
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::TriggerResetPin(){
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::PowerCycle(){
|
||||||
|
// cut the power to the MMU and after a while restore it
|
||||||
|
PowerOff();
|
||||||
|
_delay(1000); //@@TODO
|
||||||
|
PowerOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::PowerOff(){
|
||||||
|
power_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::PowerOn(){
|
||||||
|
power_on();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::mmu_loop() {
|
||||||
|
// We only leave this method if the current command was successfully completed - that's the Marlin's way of blocking operation
|
||||||
|
// Atomic compare_exchange would have been the most appropriate solution here, but this gets called only in Marlin's task,
|
||||||
|
// so thread safety should be kept
|
||||||
|
static bool avoidRecursion = false;
|
||||||
|
if (avoidRecursion)
|
||||||
|
return;
|
||||||
|
avoidRecursion = true;
|
||||||
|
|
||||||
|
logicStepLastStatus = LogicStep(); // it looks like the mmu_loop doesn't need to be a blocking call
|
||||||
|
|
||||||
|
avoidRecursion = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReportingRAII {
|
||||||
|
CommandInProgress cip;
|
||||||
|
inline ReportingRAII(CommandInProgress cip):cip(cip){
|
||||||
|
BeginReport(cip, (uint16_t)ProgressCode::EngagingIdler);
|
||||||
|
}
|
||||||
|
inline ~ReportingRAII(){
|
||||||
|
EndReport(cip, (uint16_t)ProgressCode::OK);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool MMU2::WaitForMMUReady(){
|
||||||
|
switch(State()){
|
||||||
|
case xState::Stopped:
|
||||||
|
return false;
|
||||||
|
case xState::Connecting:
|
||||||
|
// shall we wait until the MMU reconnects?
|
||||||
|
// fire-up a fsm_dlg and show "MMU not responding"?
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::tool_change(uint8_t index) {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (index != extruder) {
|
||||||
|
ReportingRAII rep(CommandInProgress::ToolChange);
|
||||||
|
BlockRunoutRAII blockRunout;
|
||||||
|
|
||||||
|
st_synchronize();
|
||||||
|
|
||||||
|
logic.ToolChange(index); // let the MMU pull the filament out and push a new one in
|
||||||
|
manage_response(false, false); // true, true);
|
||||||
|
|
||||||
|
// reset current position to whatever the planner thinks it is
|
||||||
|
// @@TODO is there some "standard" way of doing this?
|
||||||
|
//@@TODO current_position[E_AXIS] = Planner::get_machine_position_mm()[3];
|
||||||
|
|
||||||
|
extruder = index; //filament change is finished
|
||||||
|
SetActiveExtruder(0);
|
||||||
|
|
||||||
|
// @@TODO really report onto the serial? May be for the Octoprint? Not important now
|
||||||
|
// SERIAL_ECHO_START();
|
||||||
|
// SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, int(extruder));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle special T?/Tx/Tc commands
|
||||||
|
///
|
||||||
|
///- T? Gcode to extrude shouldn't have to follow, load to extruder wheels is done automatically
|
||||||
|
///- Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load.
|
||||||
|
///- Tc Load to nozzle after filament was prepared by Tx and extruder nozzle is already heated.
|
||||||
|
bool MMU2::tool_change(const char *special) {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#if 0 //@@TODO
|
||||||
|
BlockRunoutRAII blockRunout;
|
||||||
|
|
||||||
|
switch (*special) {
|
||||||
|
case '?': {
|
||||||
|
uint8_t index = 0; // mmu2_choose_filament(); //@@TODO GUI - user selects
|
||||||
|
while (!thermalManager.wait_for_hotend(active_extruder, false))
|
||||||
|
safe_delay(100);
|
||||||
|
load_filament_to_nozzle(index);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'x': {
|
||||||
|
planner.synchronize();
|
||||||
|
uint8_t index = 0; //mmu2_choose_filament(); //@@TODO GUI - user selects
|
||||||
|
disable_E0();
|
||||||
|
logic.ToolChange(index);
|
||||||
|
manage_response(false, false); // true, true);
|
||||||
|
|
||||||
|
enable_E0();
|
||||||
|
extruder = index;
|
||||||
|
SetActiveExtruder(0);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'c': {
|
||||||
|
while (!thermalManager.wait_for_hotend(active_extruder, false))
|
||||||
|
safe_delay(100);
|
||||||
|
execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence));
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MMU2::get_current_tool() {
|
||||||
|
return extruder == MMU2_NO_TOOL ? -1 : extruder;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::set_filament_type(uint8_t index, uint8_t type) {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// @@TODO - this is not supported in the new MMU yet
|
||||||
|
// cmd_arg = filamentType;
|
||||||
|
// command(MMU_CMD_F0 + index);
|
||||||
|
|
||||||
|
manage_response(false, false); // true, true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::unload() {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// @@TODO
|
||||||
|
// if (thermalManager.tooColdToExtrude(active_extruder)) {
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
// LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
{
|
||||||
|
ReportingRAII rep(CommandInProgress::UnloadFilament);
|
||||||
|
filament_ramming();
|
||||||
|
|
||||||
|
logic.UnloadFilament();
|
||||||
|
manage_response(false, false); // false, true);
|
||||||
|
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
|
||||||
|
// no active tool
|
||||||
|
extruder = MMU2_NO_TOOL;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::cut_filament(uint8_t index){
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ReportingRAII rep(CommandInProgress::CutFilament);
|
||||||
|
logic.CutFilament(index);
|
||||||
|
manage_response(false, false); // false, true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::load_filament(uint8_t index) {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ReportingRAII rep(CommandInProgress::LoadFilament);
|
||||||
|
logic.LoadFilament(index);
|
||||||
|
manage_response(false, false);
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoadingToNozzleRAII {
|
||||||
|
MMU2 &mmu2;
|
||||||
|
inline LoadingToNozzleRAII(MMU2 &mmu2):mmu2(mmu2){
|
||||||
|
mmu2.loadingToNozzle = true;
|
||||||
|
}
|
||||||
|
inline ~LoadingToNozzleRAII(){
|
||||||
|
mmu2.loadingToNozzle = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool MMU2::load_filament_to_nozzle(uint8_t index) {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LoadingToNozzleRAII ln(*this);
|
||||||
|
|
||||||
|
// if (0){ // @@TODO DEBUG
|
||||||
|
|
||||||
|
// @@TODO how is this supposed to be done in 8bit FW?
|
||||||
|
/* if (thermalManager.tooColdToExtrude(active_extruder)) {
|
||||||
|
BUZZ(200, 404);
|
||||||
|
LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD);
|
||||||
|
return false;
|
||||||
|
} else*/ {
|
||||||
|
// used for MMU-menu operation "Load to Nozzle"
|
||||||
|
ReportingRAII rep(CommandInProgress::ToolChange);
|
||||||
|
BlockRunoutRAII blockRunout;
|
||||||
|
|
||||||
|
if( extruder != MMU2_NO_TOOL ){ // we already have some filament loaded - free it + shape its tip properly
|
||||||
|
filament_ramming();
|
||||||
|
}
|
||||||
|
|
||||||
|
logic.ToolChange(index);
|
||||||
|
manage_response(false, false); // true, true);
|
||||||
|
|
||||||
|
// reset current position to whatever the planner thinks it is
|
||||||
|
// @@TODO is there some "standard" way of doing this?
|
||||||
|
//@@TODO current_position[E_AXIS] = Planner::get_machine_position_mm()[3];
|
||||||
|
|
||||||
|
extruder = index;
|
||||||
|
SetActiveExtruder(0);
|
||||||
|
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::eject_filament(uint8_t index, bool recover) {
|
||||||
|
if( ! WaitForMMUReady())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//@@TODO
|
||||||
|
// if (thermalManager.tooColdToExtrude(active_extruder)) {
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
// LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
ReportingRAII rep(CommandInProgress::EjectFilament);
|
||||||
|
current_position[E_AXIS] -= MMU2_FILAMENTCHANGE_EJECT_FEED;
|
||||||
|
//@@TODO line_to_current_position(2500 / 60);
|
||||||
|
st_synchronize();
|
||||||
|
logic.EjectFilament(index);
|
||||||
|
manage_response(false, false);
|
||||||
|
|
||||||
|
if (recover) {
|
||||||
|
// LCD_MESSAGEPGM(MSG_MMU2_EJECT_RECOVER);
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
|
||||||
|
//@@TODO wait_for_user = true;
|
||||||
|
|
||||||
|
//#if ENABLED(HOST_PROMPT_SUPPORT)
|
||||||
|
// host_prompt_do(PROMPT_USER_CONTINUE, PSTR("MMU2 Eject Recover"), PSTR("Continue"));
|
||||||
|
//#endif
|
||||||
|
//#if ENABLED(EXTENSIBLE_UI)
|
||||||
|
// ExtUI::onUserConfirmRequired_P(PSTR("MMU2 Eject Recover"));
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
//@@TODO while (wait_for_user) idle(true);
|
||||||
|
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
|
||||||
|
// logic.Command(); //@@TODO command(MMU_CMD_R0);
|
||||||
|
manage_response(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@@TODO ui.reset_status();
|
||||||
|
|
||||||
|
// no active tool
|
||||||
|
extruder = MMU2_NO_TOOL;
|
||||||
|
|
||||||
|
// BUZZ(200, 404);
|
||||||
|
|
||||||
|
// disable_E0();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::Button(uint8_t index){
|
||||||
|
logic.Button(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::Home(uint8_t mode){
|
||||||
|
logic.Home(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::SaveAndPark(bool move_axes, bool turn_off_nozzle) {
|
||||||
|
//@@TODO static constexpr xyz_pos_t park_point = NOZZLE_PARK_POINT_M600;
|
||||||
|
// if (!mmu_print_saved) { // First occurrence. Save current position, park print head, disable nozzle heater.
|
||||||
|
// LogEchoEvent("Saving and parking");
|
||||||
|
// st_synchronize();
|
||||||
|
|
||||||
|
// mmu_print_saved = true;
|
||||||
|
|
||||||
|
// resume_hotend_temp = thermalManager.degTargetHotend(active_extruder);
|
||||||
|
// resume_position = current_position;
|
||||||
|
|
||||||
|
// if (move_axes && all_axes_homed())
|
||||||
|
// nozzle.park(2, park_point);
|
||||||
|
|
||||||
|
// if (turn_off_nozzle){
|
||||||
|
// LogEchoEvent("Heater off");
|
||||||
|
// thermalManager.setTargetHotend(0, active_extruder);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // keep the motors powered forever (until some other strategy is chosen)
|
||||||
|
// gcode.reset_stepper_timeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::ResumeAndUnPark(bool move_axes, bool turn_off_nozzle) {
|
||||||
|
if (mmu_print_saved) {
|
||||||
|
LogEchoEvent("Resuming print");
|
||||||
|
|
||||||
|
if (turn_off_nozzle && resume_hotend_temp) {
|
||||||
|
MMU2_ECHO_MSG("Restoring hotend temperature ");
|
||||||
|
SERIAL_ECHOLN(resume_hotend_temp);
|
||||||
|
//@@TODO thermalManager.setTargetHotend(resume_hotend_temp, active_extruder);
|
||||||
|
|
||||||
|
// while (!thermalManager.wait_for_hotend(active_extruder, false)){
|
||||||
|
// safe_delay(1000);
|
||||||
|
// }
|
||||||
|
LogEchoEvent("Hotend temperature reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
//@@TODO if (move_axes && all_axes_homed()) {
|
||||||
|
// LogEchoEvent("Resuming XYZ");
|
||||||
|
|
||||||
|
// // Move XY to starting position, then Z
|
||||||
|
// do_blocking_move_to_xy(resume_position, feedRate_t(NOZZLE_PARK_XY_FEEDRATE));
|
||||||
|
|
||||||
|
// // Move Z_AXIS to saved position
|
||||||
|
// do_blocking_move_to_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
|
||||||
|
// } else {
|
||||||
|
// LogEchoEvent("NOT resuming XYZ");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::CheckUserInput(){
|
||||||
|
auto btn = ButtonPressed((uint16_t)lastErrorCode);
|
||||||
|
switch (btn) {
|
||||||
|
case Left:
|
||||||
|
case Middle:
|
||||||
|
case Right:
|
||||||
|
Button(btn);
|
||||||
|
break;
|
||||||
|
case RestartMMU:
|
||||||
|
Reset(CutThePower);
|
||||||
|
break;
|
||||||
|
case StopPrint:
|
||||||
|
// @@TODO not sure if we shall handle this high level operation at this spot
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Originally, this was used to wait for response and deal with timeout if necessary.
|
||||||
|
/// The new protocol implementation enables much nicer and intense reporting, so this method will boil down
|
||||||
|
/// just to verify the result of an issued command (which was basically the original idea)
|
||||||
|
///
|
||||||
|
/// It is closely related to mmu_loop() (which corresponds to our ProtocolLogic::Step()), which does NOT perform any blocking wait for a command to finish.
|
||||||
|
/// But - in case of an error, the command is not yet finished, but we must react accordingly - move the printhead elsewhere, stop heating, eat a cat or so.
|
||||||
|
/// That's what's being done here...
|
||||||
|
void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
||||||
|
mmu_print_saved = false;
|
||||||
|
|
||||||
|
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// in our new implementation, we know the exact state of the MMU at any moment, we do not have to wait for a timeout
|
||||||
|
// So in this case we shall decide if the operation is:
|
||||||
|
// - still running -> wait normally in idle()
|
||||||
|
// - failed -> then do the safety moves on the printer like before
|
||||||
|
// - finished ok -> proceed with reading other commands
|
||||||
|
|
||||||
|
// @@TODO this needs verification - we need something which matches Marlin2's idle()
|
||||||
|
manage_inactivity(true); // calls LogicStep() and remembers its return status
|
||||||
|
|
||||||
|
switch (logicStepLastStatus) {
|
||||||
|
case Finished:
|
||||||
|
// command/operation completed, let Marlin continue its work
|
||||||
|
// the E may have some more moves to finish - wait for them
|
||||||
|
st_synchronize();
|
||||||
|
return;
|
||||||
|
case VersionMismatch: // this basically means the MMU will be disabled until reconnected
|
||||||
|
return;
|
||||||
|
case CommunicationTimeout:
|
||||||
|
case CommandError:
|
||||||
|
case ProtocolError:
|
||||||
|
SaveAndPark(move_axes, turn_off_nozzle); // and wait for the user to resolve the problem
|
||||||
|
CheckUserInput();
|
||||||
|
break;
|
||||||
|
case CommunicationRecovered: // @@TODO communication recovered and may be an error recovered as well
|
||||||
|
// may be the logic layer can detect the change of state a respond with one "Recovered" to be handled here
|
||||||
|
ResumeAndUnPark(move_axes, turn_off_nozzle);
|
||||||
|
break;
|
||||||
|
case Processing: // wait for the MMU to respond
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus MMU2::LogicStep() {
|
||||||
|
StepStatus ss = logic.Step();
|
||||||
|
switch (ss) {
|
||||||
|
case Finished:
|
||||||
|
case Processing:
|
||||||
|
OnMMUProgressMsg(logic.Progress());
|
||||||
|
break;
|
||||||
|
case CommandError:
|
||||||
|
ReportError(logic.Error());
|
||||||
|
break;
|
||||||
|
case CommunicationTimeout:
|
||||||
|
state = xState::Connecting;
|
||||||
|
ReportError(ErrorCode::MMU_NOT_RESPONDING);
|
||||||
|
break;
|
||||||
|
case ProtocolError:
|
||||||
|
state = xState::Connecting;
|
||||||
|
ReportError(ErrorCode::PROTOCOL_ERROR);
|
||||||
|
break;
|
||||||
|
case VersionMismatch:
|
||||||
|
StopKeepPowered();
|
||||||
|
ReportError(ErrorCode::VERSION_MISMATCH);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( logic.Running() ){
|
||||||
|
state = xState::Active;
|
||||||
|
}
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::filament_ramming() {
|
||||||
|
execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::execute_extruder_sequence(const E_Step *sequence, int steps) {
|
||||||
|
|
||||||
|
st_synchronize();
|
||||||
|
|
||||||
|
const E_Step *step = sequence;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < steps; i++) {
|
||||||
|
const float es = pgm_read_float(&(step->extrude));
|
||||||
|
const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate));
|
||||||
|
|
||||||
|
// DEBUG_ECHO_START();
|
||||||
|
// DEBUG_ECHOLNPAIR("E step ", es, "/", fr_mm_m);
|
||||||
|
|
||||||
|
current_position[E_AXIS] += es;
|
||||||
|
// line_to_current_position(MMM_TO_MMS(fr_mm_m));
|
||||||
|
st_synchronize();
|
||||||
|
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable_E0();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::SetActiveExtruder(uint8_t ex){
|
||||||
|
active_extruder = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int strlen_constexpr(const char* str){
|
||||||
|
return *str ? 1 + strlen_constexpr(str + 1) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::ReportError(ErrorCode ec) {
|
||||||
|
// Due to a potential lossy error reporting layers linked to this hook
|
||||||
|
// we'd better report everything to make sure especially the error states
|
||||||
|
// do not get lost.
|
||||||
|
// - The good news here is the fact, that the MMU reports the errors repeatedly until resolved.
|
||||||
|
// - The bad news is, that MMU not responding may repeatedly occur on printers not having the MMU at all.
|
||||||
|
//
|
||||||
|
// Not sure how to properly handle this situation, options:
|
||||||
|
// - skip reporting "MMU not responding" (at least for now)
|
||||||
|
// - report only changes of states (we can miss an error message)
|
||||||
|
// - may be some combination of MMUAvailable + UseMMU flags and decide based on their state
|
||||||
|
// Right now the filtering of MMU_NOT_RESPONDING is done in ReportErrorHook() as it is not a problem if mmu2.cpp
|
||||||
|
ReportErrorHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)ec);
|
||||||
|
|
||||||
|
if( ec != lastErrorCode ){ // deduplicate: only report changes in error codes into the log
|
||||||
|
lastErrorCode = ec;
|
||||||
|
|
||||||
|
// Log error format: MMU2:E=32766 TextDescription
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg, sizeof(msg), "MMU2:E=%hu", (uint16_t)ec);
|
||||||
|
// Append a human readable form of the error code(s)
|
||||||
|
TranslateErr((uint16_t)ec, msg, sizeof(msg));
|
||||||
|
|
||||||
|
// beware - the prefix in the message ("MMU2") will get stripped by the logging subsystem
|
||||||
|
// and a correct MMU2 component will be assigned accordingly - see appmain.cpp
|
||||||
|
// Therefore I'm not calling MMU2_ERROR_MSG or MMU2_ECHO_MSG here
|
||||||
|
SERIAL_ECHO_START;
|
||||||
|
SERIAL_ECHOLN(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(mmu2Magic[0] == 'M'
|
||||||
|
&& mmu2Magic[1] == 'M'
|
||||||
|
&& mmu2Magic[2] == 'U'
|
||||||
|
&& mmu2Magic[3] == '2'
|
||||||
|
&& mmu2Magic[4] == ':'
|
||||||
|
&& strlen_constexpr(mmu2Magic) == 5,
|
||||||
|
"MMU2 logging prefix mismatch, must be updated at various spots"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::ReportProgress(ProgressCode pc) {
|
||||||
|
ReportProgressHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)pc);
|
||||||
|
|
||||||
|
// Log progress - example: MMU2:P=123 EngageIdler
|
||||||
|
char msg[64];
|
||||||
|
snprintf(msg, sizeof(msg), "MMU2:P=%hu", (uint16_t)pc);
|
||||||
|
// Append a human readable form of the progress code
|
||||||
|
TranslateProgress((uint16_t)pc, msg, sizeof(msg));
|
||||||
|
|
||||||
|
SERIAL_ECHO_START;
|
||||||
|
SERIAL_ECHOLN(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::OnMMUProgressMsg(ProgressCode pc){
|
||||||
|
if( pc != lastProgressCode){
|
||||||
|
ReportProgress(pc);
|
||||||
|
lastProgressCode = pc;
|
||||||
|
|
||||||
|
// Act accordingly - one-time handling
|
||||||
|
switch(pc){
|
||||||
|
case ProgressCode::FeedingToBondtech:
|
||||||
|
// prepare for the movement of the E-motor
|
||||||
|
st_synchronize();
|
||||||
|
//@@TODO sync_plan_position();
|
||||||
|
// enable_E0();
|
||||||
|
loadFilamentStarted = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing yet
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Act accordingly - every status change (even the same state)
|
||||||
|
switch(pc){
|
||||||
|
case ProgressCode::FeedingToBondtech:
|
||||||
|
if( WhereIsFilament() == FilamentState::AT_FSENSOR && loadFilamentStarted){// fsensor triggered, move the extruder to help loading
|
||||||
|
// rotate the extruder motor - no planner sync, just add more moves - as long as they are roughly at the same speed as the MMU is pushing,
|
||||||
|
// it really doesn't matter
|
||||||
|
// char tmp[64]; // @@TODO this shouldn't be needed anymore, but kept here in case of something strange
|
||||||
|
// // happens in Marlin again
|
||||||
|
// snprintf(tmp,sizeof (tmp), "E moveTo=%4.1f f=%4.0f s=%hu\n", current_position.e, feedrate_mm_s, feedrate_percentage);
|
||||||
|
// MMU2_ECHO_MSG(tmp);
|
||||||
|
|
||||||
|
// Ideally we'd use:
|
||||||
|
// line_to_current_position(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||||
|
// However, as it ignores MBL completely (which I don't care about in case of E-movement),
|
||||||
|
// we need to take the raw Z coordinates and only add some movement to E
|
||||||
|
// otherwise we risk planning a very short Z move with an extremely long E-move,
|
||||||
|
// which obviously ends up in a disaster (over/underflow of E/Z steps).
|
||||||
|
// The problem becomes obvious in Planner::_populate_block when computing da, db, dc like this:
|
||||||
|
// const int32_t da = target.a - position.a, db = target.b - position.b, dc = target.c - position.c;
|
||||||
|
// And since current_position[3] != position_float[3], we get a tiny move in Z, which is something I really want to avoid here
|
||||||
|
// @@TODO is there a "standard" way of doing this?
|
||||||
|
//@@TODO xyze_pos_t tgt = Planner::get_machine_position_mm();
|
||||||
|
const float e = loadingToNozzle ? MMU2_LOAD_TO_NOZZLE_LENGTH : MMU2_TOOL_CHANGE_LOAD_LENGTH;
|
||||||
|
//@@TODO tgt[3] += e / planner.e_factor[active_extruder];
|
||||||
|
// plan_buffer_line(tgt, MMU2_LOAD_TO_NOZZLE_FEED_RATE, active_extruder); // @@TODO magic constant - must match the feedrate of the MMU
|
||||||
|
loadFilamentStarted = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing yet
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::LogErrorEvent(const char *msg){
|
||||||
|
MMU2_ERROR_MSG(msg);
|
||||||
|
SERIAL_ECHOLN();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::LogEchoEvent(const char *msg){
|
||||||
|
MMU2_ECHO_MSG(msg);
|
||||||
|
SERIAL_ECHOLN();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
/// @file
|
||||||
|
#pragma once
|
||||||
|
#include "mmu2_protocol_logic.h"
|
||||||
|
|
||||||
|
struct E_Step;
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
struct xyz_pos_t {
|
||||||
|
uint16_t xyz[3]; // @@TODO
|
||||||
|
xyz_pos_t()=default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// general MMU setup for MK3
|
||||||
|
enum : uint8_t {
|
||||||
|
FILAMENT_UNKNOWN = 0xffU
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Top-level interface between Logic and Marlin.
|
||||||
|
/// Intentionally named MMU2 to be (almost) a drop-in replacement for the previous implementation.
|
||||||
|
/// Most of the public methods share the original naming convention as well.
|
||||||
|
class MMU2 {
|
||||||
|
public:
|
||||||
|
MMU2();
|
||||||
|
|
||||||
|
/// Powers ON the MMU, then initializes the UART and protocol logic
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
enum ResetForm : uint8_t {
|
||||||
|
Software = 0, ///< sends a X0 command into the MMU, the MMU will watchdog-reset itself
|
||||||
|
ResetPin = 1, ///< trigger the reset pin of the MMU
|
||||||
|
CutThePower = 2 ///< power off and power on (that includes +5V and +24V power lines)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Perform a reset of the MMU
|
||||||
|
/// @param level physical form of the reset
|
||||||
|
void Reset(ResetForm level);
|
||||||
|
|
||||||
|
/// Power off the MMU (cut the power)
|
||||||
|
void PowerOff();
|
||||||
|
|
||||||
|
/// Power on the MMU
|
||||||
|
void PowerOn();
|
||||||
|
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
void mmu_loop();
|
||||||
|
|
||||||
|
/// The main MMU command - select a different slot
|
||||||
|
/// @param index of the slot to be selected
|
||||||
|
/// @returns false if the operation cannot be performed (Stopped)
|
||||||
|
bool tool_change(uint8_t index);
|
||||||
|
|
||||||
|
/// Handling of special Tx, Tc, T? commands
|
||||||
|
bool tool_change(const char *special);
|
||||||
|
|
||||||
|
/// Unload of filament in collaboration with the MMU.
|
||||||
|
/// That includes rotating the printer's extruder in order to release filament.
|
||||||
|
/// @returns false if the operation cannot be performed (Stopped or cold extruder)
|
||||||
|
bool unload();
|
||||||
|
|
||||||
|
/// Load (insert) filament just into the MMU (not into printer's nozzle)
|
||||||
|
/// @returns false if the operation cannot be performed (Stopped)
|
||||||
|
bool load_filament(uint8_t index);
|
||||||
|
|
||||||
|
/// Load (push) filament from the MMU into the printer's nozzle
|
||||||
|
/// @returns false if the operation cannot be performed (Stopped or cold extruder)
|
||||||
|
bool load_filament_to_nozzle(uint8_t index);
|
||||||
|
|
||||||
|
/// Move MMU's selector aside and push the selected filament forward.
|
||||||
|
/// Usable for improving filament's tip or pulling the remaining piece of filament out completely.
|
||||||
|
bool eject_filament(uint8_t index, bool recover);
|
||||||
|
|
||||||
|
/// Issue a Cut command into the MMU
|
||||||
|
/// Requires unloaded filament from the printer (obviously)
|
||||||
|
/// @returns false if the operation cannot be performed (Stopped)
|
||||||
|
bool cut_filament(uint8_t index);
|
||||||
|
|
||||||
|
/// @returns the active filament slot index (0-4) or 0xff in case of no active tool
|
||||||
|
uint8_t get_current_tool();
|
||||||
|
|
||||||
|
bool set_filament_type(uint8_t index, uint8_t type);
|
||||||
|
|
||||||
|
/// Issue a "button" click into the MMU - to be used from Error screens of the MMU
|
||||||
|
/// to select one of the 3 possible options to resolve the issue
|
||||||
|
void Button(uint8_t index);
|
||||||
|
|
||||||
|
/// Issue an explicit "homing" command into the MMU
|
||||||
|
void Home(uint8_t mode);
|
||||||
|
|
||||||
|
/// @returns current state of FINDA (true=filament present, false=filament not present)
|
||||||
|
inline bool FindaDetectsFilament()const { return logic.FindaPressed(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Perform software self-reset of the MMU (sends an X0 command)
|
||||||
|
void ResetX0();
|
||||||
|
|
||||||
|
/// Trigger reset pin of the MMU
|
||||||
|
void TriggerResetPin();
|
||||||
|
|
||||||
|
/// Perform power cycle of the MMU (cold boot)
|
||||||
|
/// Please note this is a blocking operation (sleeps for some time inside while doing the power cycle)
|
||||||
|
void PowerCycle();
|
||||||
|
|
||||||
|
/// Stop the communication, but keep the MMU powered on (for scenarios with incorrect FW version)
|
||||||
|
void StopKeepPowered();
|
||||||
|
|
||||||
|
/// Along with the mmu_loop method, this loops until a response from the MMU is received and acts upon.
|
||||||
|
/// In case of an error, it parks the print head and turns off nozzle heating
|
||||||
|
void manage_response(const bool move_axes, const bool turn_off_nozzle);
|
||||||
|
|
||||||
|
/// Performs one step of the protocol logic state machine
|
||||||
|
/// and reports progress and errors if needed to attached ExtUIs.
|
||||||
|
/// Updates the global state of MMU (Active/Connecting/Stopped) at runtime, see @ref State
|
||||||
|
StepStatus LogicStep();
|
||||||
|
|
||||||
|
void filament_ramming();
|
||||||
|
void execute_extruder_sequence(const E_Step *sequence, int steps);
|
||||||
|
void SetActiveExtruder(uint8_t ex);
|
||||||
|
|
||||||
|
/// Reports an error into attached ExtUIs
|
||||||
|
/// @param ec error code, see ErrorCode
|
||||||
|
void ReportError(ErrorCode ec);
|
||||||
|
|
||||||
|
/// Reports progress of operations into attached ExtUIs
|
||||||
|
/// @param pc progress code, see ProgressCode
|
||||||
|
void ReportProgress(ProgressCode pc);
|
||||||
|
|
||||||
|
/// Responds to a change of MMU's progress
|
||||||
|
/// - plans additional steps, e.g. starts the E-motor after fsensor trigger
|
||||||
|
void OnMMUProgressMsg(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 (through Marlin's SERIAL_ECHO stuff)
|
||||||
|
void LogEchoEvent(const char *msg);
|
||||||
|
|
||||||
|
/// Save print and park the print head
|
||||||
|
void SaveAndPark(bool move_axes, bool turn_off_nozzle);
|
||||||
|
|
||||||
|
/// Resume print (unpark, turn on heating etc.)
|
||||||
|
void ResumeAndUnPark(bool move_axes, bool turn_off_nozzle);
|
||||||
|
|
||||||
|
/// Check for any button/user input coming from the printer's UI
|
||||||
|
void CheckUserInput();
|
||||||
|
|
||||||
|
/// Entry check of all external commands.
|
||||||
|
/// It can wait until the MMU becomes ready.
|
||||||
|
/// Optionally, it can also emit/display an error screen and the user can decide what to do next.
|
||||||
|
/// @returns false if the MMU is not ready to perform the command (for whatever reason)
|
||||||
|
bool WaitForMMUReady();
|
||||||
|
|
||||||
|
ProtocolLogic logic; ///< implementation of the protocol logic layer
|
||||||
|
int extruder; ///< currently active slot in the MMU ... somewhat... not sure where to get it from yet
|
||||||
|
|
||||||
|
xyz_pos_t resume_position;
|
||||||
|
int16_t resume_hotend_temp;
|
||||||
|
|
||||||
|
ProgressCode lastProgressCode = ProgressCode::OK;
|
||||||
|
ErrorCode lastErrorCode = ErrorCode::MMU_NOT_RESPONDING;
|
||||||
|
|
||||||
|
StepStatus logicStepLastStatus;
|
||||||
|
|
||||||
|
enum xState state;
|
||||||
|
|
||||||
|
bool mmu_print_saved;
|
||||||
|
bool loadFilamentStarted;
|
||||||
|
|
||||||
|
friend struct LoadingToNozzleRAII;
|
||||||
|
/// 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// following Marlin's way of doing stuff - one and only instance of MMU implementation in the code base
|
||||||
|
/// + avoiding buggy singletons on the AVR platform
|
||||||
|
extern MMU2 mmu2;
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/// @file error_codes.h
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/// A complete set of error codes which may be a result of a high-level command/operation.
|
||||||
|
/// This header file shall be included in the printer's firmware as well as a reference,
|
||||||
|
/// therefore the error codes have been extracted to one place.
|
||||||
|
///
|
||||||
|
/// Please note the errors are intentionally coded as "negative" values (highest bit set),
|
||||||
|
/// becase they are a complement to reporting the state of the high-level state machines -
|
||||||
|
/// positive values are considered as normal progress, negative values are errors.
|
||||||
|
///
|
||||||
|
/// Please note, that multiple TMC errors can occur at once, thus they are defined as a bitmask of the higher byte.
|
||||||
|
/// Also, as there are 3 TMC drivers on the board, each error is added a bit for the corresponding TMC -
|
||||||
|
/// TMC_PULLEY_BIT, TMC_SELECTOR_BIT, TMC_IDLER_BIT,
|
||||||
|
/// The resulting error is a bitwise OR over 3 TMC drivers and their status, which should cover most of the situations correctly.
|
||||||
|
enum class ErrorCode : uint_fast16_t {
|
||||||
|
RUNNING = 0x0000, ///< the operation is still running - keep this value as ZERO as it is used for initialization of error codes as well
|
||||||
|
OK = 0x0001, ///< the operation finished OK
|
||||||
|
|
||||||
|
// TMC bit masks
|
||||||
|
TMC_PULLEY_BIT = 0x0040, ///< +64 TMC Pulley bit
|
||||||
|
TMC_SELECTOR_BIT = 0x0080, ///< +128 TMC Pulley bit
|
||||||
|
TMC_IDLER_BIT = 0x0100, ///< +256 TMC Pulley bit
|
||||||
|
|
||||||
|
/// Unload Filament related error codes
|
||||||
|
FINDA_DIDNT_SWITCH_ON = 0x8001, ///< E32769 FINDA didn't switch on while loading filament - either there is something blocking the metal ball or a cable is broken/disconnected
|
||||||
|
FINDA_DIDNT_SWITCH_OFF = 0x8002, ///< E32770 FINDA didn't switch off while unloading filament
|
||||||
|
|
||||||
|
FSENSOR_DIDNT_SWITCH_ON = 0x8003, ///< E32771 Filament sensor didn't switch on while performing LoadFilament
|
||||||
|
FSENSOR_DIDNT_SWITCH_OFF = 0x8004, ///< E32772 Filament sensor didn't switch off while performing UnloadFilament
|
||||||
|
|
||||||
|
FILAMENT_ALREADY_LOADED = 0x8005, ///< E32773 cannot perform operation LoadFilament or move the selector as the filament is already loaded
|
||||||
|
|
||||||
|
INVALID_TOOL = 0x8006, ///< E32774 tool/slot index out of range (typically issuing T5 into an MMU with just 5 slots - valid range 0-4)
|
||||||
|
|
||||||
|
HOMING_FAILED = 0x8007, ///< generic homing failed error - always reported with the corresponding axis bit set (Idler or Selector) as follows:
|
||||||
|
HOMING_SELECTOR_FAILED = HOMING_FAILED | TMC_SELECTOR_BIT, ///< E32903 the Selector was unable to home properly - that means something is blocking its movement
|
||||||
|
HOMING_IDLER_FAILED = HOMING_FAILED | TMC_IDLER_BIT, ///< E33031 the Idler was unable to home properly - that means something is blocking its movement
|
||||||
|
STALLED_PULLEY = HOMING_FAILED | TMC_PULLEY_BIT, ///< E32839 for the Pulley "homing" means just stallguard detected during Pulley's operation (Pulley doesn't home)
|
||||||
|
|
||||||
|
QUEUE_FULL = 0x802b, ///< E32811 internal logic error - attempt to move with a full queue
|
||||||
|
|
||||||
|
VERSION_MISMATCH = 0x802c, ///< E32812 internal error of the printer - incompatible version of the MMU FW
|
||||||
|
PROTOCOL_ERROR = 0x802d, ///< E32813 internal error of the printer - communication with the MMU got garbled - protocol decoder couldn't decode the incoming messages
|
||||||
|
MMU_NOT_RESPONDING = 0x802e, ///< E32814 internal error of the printer - communication with the MMU is not working
|
||||||
|
INTERNAL = 0x802f, ///< E32815 internal runtime error (software)
|
||||||
|
|
||||||
|
/// TMC driver init error - TMC dead or bad communication
|
||||||
|
/// - E33344 Pulley TMC driver
|
||||||
|
/// - E33404 Selector TMC driver
|
||||||
|
/// - E33536 Idler TMC driver
|
||||||
|
/// - E33728 All 3 TMC driver
|
||||||
|
TMC_IOIN_MISMATCH = 0x8200,
|
||||||
|
|
||||||
|
/// TMC driver reset - recoverable, we just need to rehome the axis
|
||||||
|
/// Idler: can be rehomed any time
|
||||||
|
/// Selector: if there is a filament, remove it and rehome, if there is no filament, just rehome
|
||||||
|
/// Pulley: do nothing - for the loading sequence - just restart and move slowly, for the unload sequence just restart
|
||||||
|
/// - E33856 Pulley TMC driver
|
||||||
|
/// - E33920 Selector TMC driver
|
||||||
|
/// - E34048 Idler TMC driver
|
||||||
|
/// - E34240 All 3 TMC driver
|
||||||
|
TMC_RESET = 0x8400,
|
||||||
|
|
||||||
|
/// not enough current for the TMC, NOT RECOVERABLE
|
||||||
|
/// - E34880 Pulley TMC driver
|
||||||
|
/// - E34944 Selector TMC driver
|
||||||
|
/// - E35072 Idler TMC driver
|
||||||
|
/// - E35264 All 3 TMC driver
|
||||||
|
TMC_UNDERVOLTAGE_ON_CHARGE_PUMP = 0x8800,
|
||||||
|
|
||||||
|
/// TMC driver serious error - short to ground on coil A or coil B - dangerous to recover
|
||||||
|
/// - E36928 Pulley TMC driver
|
||||||
|
/// - E36992 Selector TMC driver
|
||||||
|
/// - E37120 Idler TMC driver
|
||||||
|
/// - E37312 All 3 TMC driver
|
||||||
|
TMC_SHORT_TO_GROUND = 0x9000,
|
||||||
|
|
||||||
|
/// TMC driver over temperature warning - can be recovered by restarting the driver.
|
||||||
|
/// If this error happens, we should probably go into the error state as soon as the current command is finished.
|
||||||
|
/// The driver technically still works at this point.
|
||||||
|
/// - E41024 Pulley TMC driver
|
||||||
|
/// - E41088 Selector TMC driver
|
||||||
|
/// - E41216 Idler TMC driver
|
||||||
|
/// - E41408 All 3 TMC driver
|
||||||
|
TMC_OVER_TEMPERATURE_WARN = 0xA000,
|
||||||
|
|
||||||
|
/// TMC driver over temperature error - we really shouldn't ever reach this error.
|
||||||
|
/// It can still be recovered if the driver cools down below 120C.
|
||||||
|
/// The driver needs to be disabled and enabled again for operation to resume after this error is cleared.
|
||||||
|
/// - E49216 Pulley TMC driver
|
||||||
|
/// - E49280 Selector TMC driver
|
||||||
|
/// - E49408 Idler TMC driver
|
||||||
|
/// - E49600 All 3 TMC driver
|
||||||
|
TMC_OVER_TEMPERATURE_ERROR = 0xC000
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/// @file progress_codes.h
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/// A complete set of progress codes which may be reported while running a high-level command/operation
|
||||||
|
/// This header file shall be included in the printer's firmware as well as a reference,
|
||||||
|
/// therefore the progress codes have been extracted to one place
|
||||||
|
enum class ProgressCode : uint_fast8_t {
|
||||||
|
OK = 0, ///< finished ok
|
||||||
|
|
||||||
|
EngagingIdler, // P1
|
||||||
|
DisengagingIdler, // P2
|
||||||
|
UnloadingToFinda, // P3
|
||||||
|
UnloadingToPulley, //P4
|
||||||
|
FeedingToFinda, // P5
|
||||||
|
FeedingToBondtech, // P6
|
||||||
|
FeedingToNozzle, // P7
|
||||||
|
AvoidingGrind, // P8
|
||||||
|
FinishingMoves, // P9
|
||||||
|
|
||||||
|
ERRDisengagingIdler, // P10
|
||||||
|
ERREngagingIdler, // P11
|
||||||
|
ERRWaitingForUser, // P12
|
||||||
|
ERRInternal, // P13
|
||||||
|
ERRHelpingFilament, // P14
|
||||||
|
ERRTMCFailed, // P15
|
||||||
|
|
||||||
|
UnloadingFilament, // P16
|
||||||
|
LoadingFilament, // P17
|
||||||
|
SelectingFilamentSlot, // P18
|
||||||
|
PreparingBlade, // P19
|
||||||
|
PushingFilament, // P20
|
||||||
|
PerformingCut, // P21
|
||||||
|
ReturningSelector, // P22
|
||||||
|
ParkingSelector, // P23
|
||||||
|
EjectingFilament, // P24
|
||||||
|
RetractingFromFinda, // P25
|
||||||
|
|
||||||
|
Homing,
|
||||||
|
|
||||||
|
Empty = 0xff // dummy empty state
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "mmu2_error_converter.h"
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
// @@TODO
|
||||||
|
void TranslateErr(uint16_t ec, char *dst, size_t dstSize) { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
void TranslateErr(uint16_t ec, char *dst, size_t dstSize);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#include "mmu2_fsensor.h"
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
FilamentState WhereIsFilament(){
|
||||||
|
// @@TODO
|
||||||
|
return FilamentState::IN_NOZZLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// on AVR this does nothing
|
||||||
|
BlockRunoutRAII::BlockRunoutRAII() { }
|
||||||
|
BlockRunoutRAII::~BlockRunoutRAII() { }
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
/// Possible states of filament from the perspective of presence in various parts of the printer
|
||||||
|
/// Beware, the numeric codes are important and sent into the MMU
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
FilamentState WhereIsFilament();
|
||||||
|
|
||||||
|
/// Can be used to block printer's filament sensor handling - to avoid errorneous injecting of M600
|
||||||
|
/// while doing a toolchange with the MMU
|
||||||
|
class BlockRunoutRAII {
|
||||||
|
public:
|
||||||
|
BlockRunoutRAII();
|
||||||
|
~BlockRunoutRAII();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef UNITTEST
|
||||||
|
#include "Marlin.h"
|
||||||
|
|
||||||
|
// Beware - before changing this prefix, think twice
|
||||||
|
// you'd need to change appmain.cpp app_marlin_serial_output_write_hook
|
||||||
|
// and MMU2::ReportError + MMU2::ReportProgress
|
||||||
|
static constexpr char mmu2Magic[] PROGMEM = "MMU2:";
|
||||||
|
|
||||||
|
#define SERIAL_MMU2() { serialprintPGM(mmu2Magic); }
|
||||||
|
|
||||||
|
#define MMU2_ECHO_MSG(S) do{ SERIAL_ECHO_START; SERIAL_MMU2(); SERIAL_ECHO(S); }while(0)
|
||||||
|
#define MMU2_ERROR_MSG(S) do{ SERIAL_ERROR_START; SERIAL_MMU2(); SERIAL_ECHO(S); }while(0)
|
||||||
|
|
||||||
|
#else // #ifndef UNITTEST
|
||||||
|
|
||||||
|
#define MMU2_ECHO_MSG(S) /* */
|
||||||
|
#define MMU2_ERROR_MSG(S) /* */
|
||||||
|
#define SERIAL_ECHO(S) /* */
|
||||||
|
#define SERIAL_ECHOLN(S) /* */
|
||||||
|
|
||||||
|
#endif // #ifndef UNITTEST
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#include "mmu2_power.h"
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
// sadly, on MK3 we cannot do this on HW
|
||||||
|
void power_on() { }
|
||||||
|
|
||||||
|
void power_off() { }
|
||||||
|
|
||||||
|
void reset() { }
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
void power_on();
|
||||||
|
|
||||||
|
void power_off();
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "mmu2_progress_converter.h"
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
//@@TODO
|
||||||
|
void TranslateProgress(uint16_t pc, char *dst, size_t dstSize) { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
void TranslateProgress(uint16_t pc, char *dst, size_t dstSize);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
/// @file
|
||||||
|
#include "mmu2_protocol.h"
|
||||||
|
|
||||||
|
// protocol definition
|
||||||
|
// command: Q0
|
||||||
|
// meaning: query operation status
|
||||||
|
// Query/command: query
|
||||||
|
// Expected reply from the MMU:
|
||||||
|
// any of the running operation statuses: OID: [T|L|U|E|C|W|K][0-4]
|
||||||
|
// <OID> P[0-9] : command being processed i.e. operation running, may contain a state number
|
||||||
|
// <OID> E[0-9][0-9] : error 1-9 while doing a tool change
|
||||||
|
// <OID> F : operation finished - will be repeated to "Q" messages until a new command is issued
|
||||||
|
|
||||||
|
namespace modules {
|
||||||
|
namespace protocol {
|
||||||
|
|
||||||
|
// decoding automaton
|
||||||
|
// states: input -> transition into state
|
||||||
|
// Code QTLMUXPSBEWK -> msgcode
|
||||||
|
// \n ->start
|
||||||
|
// * ->error
|
||||||
|
// error \n ->start
|
||||||
|
// * ->error
|
||||||
|
// msgcode 0-9 ->msgvalue
|
||||||
|
// * ->error
|
||||||
|
// msgvalue 0-9 ->msgvalue
|
||||||
|
// \n ->start successfully accepted command
|
||||||
|
|
||||||
|
DecodeStatus Protocol::DecodeRequest(uint8_t c) {
|
||||||
|
switch (rqState) {
|
||||||
|
case RequestStates::Code:
|
||||||
|
switch (c) {
|
||||||
|
case 'Q':
|
||||||
|
case 'T':
|
||||||
|
case 'L':
|
||||||
|
case 'M':
|
||||||
|
case 'U':
|
||||||
|
case 'X':
|
||||||
|
case 'P':
|
||||||
|
case 'S':
|
||||||
|
case 'B':
|
||||||
|
case 'E':
|
||||||
|
case 'W':
|
||||||
|
case 'K':
|
||||||
|
case 'F':
|
||||||
|
case 'f':
|
||||||
|
case 'H':
|
||||||
|
requestMsg.code = (RequestMsgCodes)c;
|
||||||
|
requestMsg.value = 0;
|
||||||
|
rqState = RequestStates::Value;
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
default:
|
||||||
|
requestMsg.code = RequestMsgCodes::unknown;
|
||||||
|
rqState = RequestStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
case RequestStates::Value:
|
||||||
|
if (IsDigit(c)) {
|
||||||
|
requestMsg.value *= 10;
|
||||||
|
requestMsg.value += c - '0';
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
} else if (IsNewLine(c)) {
|
||||||
|
rqState = RequestStates::Code;
|
||||||
|
return DecodeStatus::MessageCompleted;
|
||||||
|
} else {
|
||||||
|
requestMsg.code = RequestMsgCodes::unknown;
|
||||||
|
rqState = RequestStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
default: //case error:
|
||||||
|
if (IsNewLine(c)) {
|
||||||
|
rqState = RequestStates::Code;
|
||||||
|
return DecodeStatus::MessageCompleted;
|
||||||
|
} else {
|
||||||
|
requestMsg.code = RequestMsgCodes::unknown;
|
||||||
|
rqState = RequestStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Protocol::EncodeRequest(const RequestMsg &msg, uint8_t *txbuff) {
|
||||||
|
constexpr uint8_t reqSize = 3;
|
||||||
|
txbuff[0] = (uint8_t)msg.code;
|
||||||
|
txbuff[1] = msg.value + '0';
|
||||||
|
txbuff[2] = '\n';
|
||||||
|
return reqSize;
|
||||||
|
static_assert(reqSize <= MaxRequestSize(), "Request message length exceeded the maximum size, increase the magic constant in MaxRequestSize()");
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeStatus Protocol::DecodeResponse(uint8_t c) {
|
||||||
|
switch (rspState) {
|
||||||
|
case ResponseStates::RequestCode:
|
||||||
|
switch (c) {
|
||||||
|
case 'Q':
|
||||||
|
case 'T':
|
||||||
|
case 'L':
|
||||||
|
case 'M':
|
||||||
|
case 'U':
|
||||||
|
case 'X':
|
||||||
|
case 'P':
|
||||||
|
case 'S':
|
||||||
|
case 'B':
|
||||||
|
case 'E':
|
||||||
|
case 'W':
|
||||||
|
case 'K':
|
||||||
|
case 'F':
|
||||||
|
case 'f':
|
||||||
|
case 'H':
|
||||||
|
responseMsg.request.code = (RequestMsgCodes)c;
|
||||||
|
responseMsg.request.value = 0;
|
||||||
|
rspState = ResponseStates::RequestValue;
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
case 0x0a:
|
||||||
|
case 0x0d:
|
||||||
|
// skip leading whitespace if any (makes integration with other SW easier/tolerant)
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
default:
|
||||||
|
rspState = ResponseStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
case ResponseStates::RequestValue:
|
||||||
|
if (IsDigit(c)) {
|
||||||
|
responseMsg.request.value *= 10;
|
||||||
|
responseMsg.request.value += c - '0';
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
} else if (c == ' ') {
|
||||||
|
rspState = ResponseStates::ParamCode;
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
} else {
|
||||||
|
rspState = ResponseStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
case ResponseStates::ParamCode:
|
||||||
|
switch (c) {
|
||||||
|
case 'P':
|
||||||
|
case 'E':
|
||||||
|
case 'F':
|
||||||
|
case 'A':
|
||||||
|
case 'R':
|
||||||
|
rspState = ResponseStates::ParamValue;
|
||||||
|
responseMsg.paramCode = (ResponseMsgParamCodes)c;
|
||||||
|
responseMsg.paramValue = 0;
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
default:
|
||||||
|
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||||
|
rspState = ResponseStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
case ResponseStates::ParamValue:
|
||||||
|
if (IsDigit(c)) {
|
||||||
|
responseMsg.paramValue *= 10;
|
||||||
|
responseMsg.paramValue += c - '0';
|
||||||
|
return DecodeStatus::NeedMoreData;
|
||||||
|
} else if (IsNewLine(c)) {
|
||||||
|
rspState = ResponseStates::RequestCode;
|
||||||
|
return DecodeStatus::MessageCompleted;
|
||||||
|
} else {
|
||||||
|
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||||
|
rspState = ResponseStates::Error;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
default: //case error:
|
||||||
|
if (IsNewLine(c)) {
|
||||||
|
rspState = ResponseStates::RequestCode;
|
||||||
|
return DecodeStatus::MessageCompleted;
|
||||||
|
} else {
|
||||||
|
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||||
|
return DecodeStatus::Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Protocol::EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff) {
|
||||||
|
txbuff[0] = (uint8_t)msg.code;
|
||||||
|
txbuff[1] = msg.value + '0';
|
||||||
|
txbuff[2] = ' ';
|
||||||
|
txbuff[3] = (uint8_t)ar;
|
||||||
|
txbuff[4] = '\n';
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Protocol::EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff) {
|
||||||
|
txbuff[0] = (uint8_t)msg.code;
|
||||||
|
txbuff[1] = msg.value + '0';
|
||||||
|
txbuff[2] = ' ';
|
||||||
|
txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
|
||||||
|
txbuff[4] = findaValue + '0';
|
||||||
|
txbuff[5] = '\n';
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Protocol::EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff) {
|
||||||
|
txbuff[0] = (uint8_t)msg.code;
|
||||||
|
txbuff[1] = msg.value + '0';
|
||||||
|
txbuff[2] = ' ';
|
||||||
|
txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
|
||||||
|
uint8_t *dst = txbuff + 4;
|
||||||
|
if (value < 10) {
|
||||||
|
*dst++ = value + '0';
|
||||||
|
} else if (value < 100) {
|
||||||
|
*dst++ = value / 10 + '0';
|
||||||
|
*dst++ = value % 10 + '0';
|
||||||
|
} else {
|
||||||
|
*dst++ = value / 100 + '0';
|
||||||
|
*dst++ = (value / 10) % 10 + '0';
|
||||||
|
*dst++ = value % 10 + '0';
|
||||||
|
}
|
||||||
|
*dst = '\n';
|
||||||
|
return dst - txbuff + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint16_t value, uint8_t *txbuff) {
|
||||||
|
txbuff[0] = (uint8_t)msg.code;
|
||||||
|
txbuff[1] = msg.value + '0';
|
||||||
|
txbuff[2] = ' ';
|
||||||
|
txbuff[3] = (uint8_t)code;
|
||||||
|
uint8_t *dst = txbuff + 4;
|
||||||
|
if (code != ResponseMsgParamCodes::Finished) {
|
||||||
|
if (value < 10) {
|
||||||
|
*dst++ = value + '0';
|
||||||
|
} else if (value < 100) {
|
||||||
|
*dst++ = value / 10 + '0';
|
||||||
|
*dst++ = value % 10 + '0';
|
||||||
|
} else if (value < 1000) {
|
||||||
|
*dst++ = value / 100 + '0';
|
||||||
|
*dst++ = (value / 10) % 10 + '0';
|
||||||
|
*dst++ = value % 10 + '0';
|
||||||
|
} else if (value < 10000) {
|
||||||
|
*dst++ = value / 1000 + '0';
|
||||||
|
*dst++ = (value / 100) % 10 + '0';
|
||||||
|
*dst++ = (value / 10) % 10 + '0';
|
||||||
|
*dst++ = value % 10 + '0';
|
||||||
|
} else {
|
||||||
|
*dst++ = value / 10000 + '0';
|
||||||
|
*dst++ = (value / 1000) % 10 + '0';
|
||||||
|
*dst++ = (value / 100) % 10 + '0';
|
||||||
|
*dst++ = (value / 10) % 10 + '0';
|
||||||
|
*dst++ = value % 10 + '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*dst = '\n';
|
||||||
|
return dst - txbuff + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace protocol
|
||||||
|
} // namespace modules
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
/// @file protocol.h
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace modules {
|
||||||
|
|
||||||
|
/// @brief The MMU communication protocol implementation and related stuff.
|
||||||
|
///
|
||||||
|
/// See description of the new protocol in the MMU 2021 doc
|
||||||
|
/// @@TODO possibly add some checksum to verify the correctness of messages
|
||||||
|
namespace protocol {
|
||||||
|
|
||||||
|
/// Definition of request message codes
|
||||||
|
enum class RequestMsgCodes : uint8_t {
|
||||||
|
unknown = 0,
|
||||||
|
Query = 'Q',
|
||||||
|
Tool = 'T',
|
||||||
|
Load = 'L',
|
||||||
|
Mode = 'M',
|
||||||
|
Unload = 'U',
|
||||||
|
Reset = 'X',
|
||||||
|
Finda = 'P',
|
||||||
|
Version = 'S',
|
||||||
|
Button = 'B',
|
||||||
|
Eject = 'E',
|
||||||
|
Wait = 'W',
|
||||||
|
Cut = 'K',
|
||||||
|
FilamentType = 'F',
|
||||||
|
FilamentSensor = 'f',
|
||||||
|
Home = 'H'
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Definition of response message parameter codes
|
||||||
|
enum class ResponseMsgParamCodes : uint8_t {
|
||||||
|
unknown = 0,
|
||||||
|
Processing = 'P',
|
||||||
|
Error = 'E',
|
||||||
|
Finished = 'F',
|
||||||
|
Accepted = 'A',
|
||||||
|
Rejected = 'R'
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A request message - requests are being sent by the printer into the MMU.
|
||||||
|
struct RequestMsg {
|
||||||
|
RequestMsgCodes code; ///< code of the request message
|
||||||
|
uint8_t value; ///< value of the request message
|
||||||
|
|
||||||
|
/// @param code of the request message
|
||||||
|
/// @param value of the request message
|
||||||
|
inline RequestMsg(RequestMsgCodes code, uint8_t value)
|
||||||
|
: code(code)
|
||||||
|
, value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A response message - responses are being sent from the MMU into the printer as a response to a request message.
|
||||||
|
struct ResponseMsg {
|
||||||
|
RequestMsg request; ///< response is always preceeded by the request message
|
||||||
|
ResponseMsgParamCodes paramCode; ///< code of the parameter
|
||||||
|
uint16_t paramValue; ///< value of the parameter
|
||||||
|
|
||||||
|
/// @param request the source request message this response is a reply to
|
||||||
|
/// @param paramCode code of the parameter
|
||||||
|
/// @param paramValue value of the parameter
|
||||||
|
inline ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint16_t paramValue)
|
||||||
|
: request(request)
|
||||||
|
, paramCode(paramCode)
|
||||||
|
, paramValue(paramValue) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Message decoding return values
|
||||||
|
enum class DecodeStatus : uint_fast8_t {
|
||||||
|
MessageCompleted, ///< message completed and successfully lexed
|
||||||
|
NeedMoreData, ///< message incomplete yet, waiting for another byte to come
|
||||||
|
Error, ///< input character broke message decoding
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Protocol class is responsible for creating/decoding messages in Rx/Tx buffer
|
||||||
|
///
|
||||||
|
/// Beware - in the decoding more, it is meant to be a statefull instance which works through public methods
|
||||||
|
/// processing one input byte per call.
|
||||||
|
class Protocol {
|
||||||
|
public:
|
||||||
|
inline Protocol()
|
||||||
|
: rqState(RequestStates::Code)
|
||||||
|
, requestMsg(RequestMsgCodes::unknown, 0)
|
||||||
|
, rspState(ResponseStates::RequestCode)
|
||||||
|
, responseMsg(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the input byte c and steps one step through the state machine
|
||||||
|
/// @returns state of the message being decoded
|
||||||
|
DecodeStatus DecodeRequest(uint8_t c);
|
||||||
|
|
||||||
|
/// Decodes response message in rxbuff
|
||||||
|
/// @returns decoded response message structure
|
||||||
|
DecodeStatus DecodeResponse(uint8_t c);
|
||||||
|
|
||||||
|
/// Encodes request message msg into txbuff memory
|
||||||
|
/// It is expected the txbuff is large enough to fit the message
|
||||||
|
/// @returns number of bytes written into txbuff
|
||||||
|
static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff);
|
||||||
|
|
||||||
|
/// @returns the maximum byte length necessary to encode a request message
|
||||||
|
/// Beneficial in case of pre-allocating a buffer for enconding a RequestMsg.
|
||||||
|
static constexpr uint8_t MaxRequestSize() { return 3; }
|
||||||
|
|
||||||
|
/// Encode generic response Command Accepted or Rejected
|
||||||
|
/// @param msg source request message for this response
|
||||||
|
/// @param ar code of response parameter
|
||||||
|
/// @param txbuff where to format the message
|
||||||
|
/// @returns number of bytes written into txbuff
|
||||||
|
static uint8_t EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff);
|
||||||
|
|
||||||
|
/// Encode response to Read FINDA query
|
||||||
|
/// @param msg source request message for this response
|
||||||
|
/// @param findaValue 1/0 (on/off) status of FINDA
|
||||||
|
/// @param txbuff where to format the message
|
||||||
|
/// @returns number of bytes written into txbuff
|
||||||
|
static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff);
|
||||||
|
|
||||||
|
/// Encode response to Version query
|
||||||
|
/// @param msg source request message for this response
|
||||||
|
/// @param value version number (0-255)
|
||||||
|
/// @param txbuff where to format the message
|
||||||
|
/// @returns number of bytes written into txbuff
|
||||||
|
static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff);
|
||||||
|
|
||||||
|
/// Encode response to Query operation status
|
||||||
|
/// @param msg source request message for this response
|
||||||
|
/// @param code status of operation (Processing, Error, Finished)
|
||||||
|
/// @param value related to status of operation(e.g. error code or progress)
|
||||||
|
/// @param txbuff where to format the message
|
||||||
|
/// @returns number of bytes written into txbuff
|
||||||
|
static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint16_t value, uint8_t *txbuff);
|
||||||
|
|
||||||
|
/// @returns the most recently lexed request message
|
||||||
|
inline const RequestMsg GetRequestMsg() const { return requestMsg; }
|
||||||
|
|
||||||
|
/// @returns the most recently lexed response message
|
||||||
|
inline const ResponseMsg GetResponseMsg() const { return responseMsg; }
|
||||||
|
|
||||||
|
/// resets the internal request decoding state (typically after an error)
|
||||||
|
void ResetRequestDecoder() {
|
||||||
|
rqState = RequestStates::Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// resets the internal response decoding state (typically after an error)
|
||||||
|
void ResetResponseDecoder() {
|
||||||
|
rspState = ResponseStates::RequestCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class RequestStates : uint8_t {
|
||||||
|
Code, ///< starting state - expects message code
|
||||||
|
Value, ///< expecting code value
|
||||||
|
Error ///< automaton in error state
|
||||||
|
};
|
||||||
|
|
||||||
|
RequestStates rqState;
|
||||||
|
RequestMsg requestMsg;
|
||||||
|
|
||||||
|
enum class ResponseStates : uint8_t {
|
||||||
|
RequestCode, ///< starting state - expects message code
|
||||||
|
RequestValue, ///< expecting code value
|
||||||
|
ParamCode, ///< expecting param code
|
||||||
|
ParamValue, ///< expecting param value
|
||||||
|
Error ///< automaton in error state
|
||||||
|
};
|
||||||
|
|
||||||
|
ResponseStates rspState;
|
||||||
|
ResponseMsg responseMsg;
|
||||||
|
|
||||||
|
static bool IsNewLine(uint8_t c) {
|
||||||
|
return c == '\n' || c == '\r';
|
||||||
|
}
|
||||||
|
static bool IsDigit(uint8_t c) {
|
||||||
|
return c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace protocol
|
||||||
|
} // namespace modules
|
||||||
|
|
||||||
|
namespace mp = modules::protocol;
|
||||||
|
|
@ -0,0 +1,564 @@
|
||||||
|
#include "mmu2_protocol_logic.h"
|
||||||
|
#include "mmu2_log.h"
|
||||||
|
#include "mmu2_fsensor.h"
|
||||||
|
#include "system_timer.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
StepStatus ProtocolLogicPartBase::ProcessFINDAReqSent(StepStatus finishedRV, State nextState){
|
||||||
|
auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
if (expmsg != MessageReady)
|
||||||
|
return expmsg;
|
||||||
|
logic->findaPressed = logic->rsp.paramValue;
|
||||||
|
state = nextState;
|
||||||
|
return finishedRV;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogicPartBase::CheckAndReportAsyncEvents(){
|
||||||
|
// even when waiting for a query period, we need to report a change in filament sensor's state
|
||||||
|
// - it is vital for a precise synchronization of moves of the printer and the MMU
|
||||||
|
uint8_t fs = (uint8_t)WhereIsFilament();
|
||||||
|
if( fs != logic->lastFSensor ){
|
||||||
|
SendAndUpdateFilamentSensor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogicPartBase::SendQuery(){
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
|
||||||
|
state = State::QuerySent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogicPartBase::SendFINDAQuery(){
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) );
|
||||||
|
state = State::FINDAReqSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogicPartBase::SendAndUpdateFilamentSensor(){
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, logic->lastFSensor = (uint8_t)WhereIsFilament() ) );
|
||||||
|
state = State::FilamentSensorStateSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogicPartBase::SendButton(uint8_t btn){
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
|
||||||
|
state = State::ButtonSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus ProtocolLogic::ProcessUARTByte(uint8_t c) {
|
||||||
|
switch (protocol.DecodeResponse(c)) {
|
||||||
|
case DecodeStatus::MessageCompleted:
|
||||||
|
// @@TODO reset direction of communication
|
||||||
|
return MessageReady;
|
||||||
|
case DecodeStatus::NeedMoreData:
|
||||||
|
return Processing;
|
||||||
|
case DecodeStatus::Error:
|
||||||
|
default:
|
||||||
|
return ProtocolError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus ProtocolLogic::ExpectingMessage(uint32_t timeout) {
|
||||||
|
int bytesConsumed = 0;
|
||||||
|
int c = -1;
|
||||||
|
|
||||||
|
// try to consume as many rx bytes as possible (until a message has been completed)
|
||||||
|
while((c = uart->read()) >= 0){
|
||||||
|
++bytesConsumed;
|
||||||
|
RecordReceivedByte(c);
|
||||||
|
switch (protocol.DecodeResponse(c)) {
|
||||||
|
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:
|
||||||
|
break;
|
||||||
|
case DecodeStatus::Error:
|
||||||
|
default:
|
||||||
|
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||||
|
return ProtocolError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( bytesConsumed != 0 ){
|
||||||
|
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||||
|
return Processing; // consumed some bytes, but message still not ready
|
||||||
|
} else if (Elapsed(timeout)) {
|
||||||
|
return CommunicationTimeout;
|
||||||
|
}
|
||||||
|
return Processing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::SendMsg(RequestMsg rq) {
|
||||||
|
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||||
|
uint8_t len = Protocol::EncodeRequest(rq, txbuff);
|
||||||
|
uart->write(txbuff, len);
|
||||||
|
LogRequestMsg(txbuff, len);
|
||||||
|
RecordUARTActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartSeq::Restart() {
|
||||||
|
state = State::S0Sent;
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus StartSeq::Step() {
|
||||||
|
auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
if (expmsg != MessageReady)
|
||||||
|
return expmsg;
|
||||||
|
|
||||||
|
// solve initial handshake
|
||||||
|
switch (state) {
|
||||||
|
case State::S0Sent: // received response to S0 - major
|
||||||
|
if (logic->rsp.paramValue != 2) {
|
||||||
|
return VersionMismatch;
|
||||||
|
}
|
||||||
|
logic->dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 1));
|
||||||
|
state = State::S1Sent;
|
||||||
|
break;
|
||||||
|
case State::S1Sent: // received response to S1 - minor
|
||||||
|
if (logic->rsp.paramValue != 0) {
|
||||||
|
return VersionMismatch;
|
||||||
|
}
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 2));
|
||||||
|
state = State::S2Sent;
|
||||||
|
break;
|
||||||
|
case State::S2Sent: // received response to S2 - revision
|
||||||
|
if (logic->rsp.paramValue != 0) {
|
||||||
|
return VersionMismatch;
|
||||||
|
}
|
||||||
|
// Start General Interrogation after line up.
|
||||||
|
// For now we just send the state of the filament sensor, but we may request
|
||||||
|
// data point states from the MMU as well. TBD in the future, especially with another protocol
|
||||||
|
SendAndUpdateFilamentSensor();
|
||||||
|
break;
|
||||||
|
case State::FilamentSensorStateSent:
|
||||||
|
state = State::Ready;
|
||||||
|
return Finished;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return VersionMismatch;
|
||||||
|
}
|
||||||
|
return Processing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Command::Restart() {
|
||||||
|
state = State::CommandSent;
|
||||||
|
logic->SendMsg(logic->command.rq);
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus Command::Step() {
|
||||||
|
switch (state) {
|
||||||
|
case State::CommandSent: {
|
||||||
|
auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
if (expmsg != MessageReady)
|
||||||
|
return expmsg;
|
||||||
|
|
||||||
|
switch (logic->rsp.paramCode) { // the response should be either accepted or rejected
|
||||||
|
case ResponseMsgParamCodes::Accepted:
|
||||||
|
logic->progressCode = ProgressCode::OK;
|
||||||
|
logic->errorCode = ErrorCode::RUNNING;
|
||||||
|
state = State::Wait;
|
||||||
|
break;
|
||||||
|
case ResponseMsgParamCodes::Rejected:
|
||||||
|
// rejected - should normally not happen, but report the error up
|
||||||
|
logic->progressCode = ProgressCode::OK;
|
||||||
|
logic->errorCode = ErrorCode::PROTOCOL_ERROR;
|
||||||
|
return CommandRejected;
|
||||||
|
default:
|
||||||
|
return ProtocolError;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case State::Wait:
|
||||||
|
if (logic->Elapsed(heartBeatPeriod)) {
|
||||||
|
SendQuery();
|
||||||
|
} else {
|
||||||
|
// even when waiting for a query period, we need to report a change in filament sensor's state
|
||||||
|
// - it is vital for a precise synchronization of moves of the printer and the MMU
|
||||||
|
CheckAndReportAsyncEvents();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::QuerySent: {
|
||||||
|
auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
if (expmsg != MessageReady)
|
||||||
|
return expmsg;
|
||||||
|
}
|
||||||
|
// [[fallthrough]];
|
||||||
|
case State::ContinueFromIdle:
|
||||||
|
switch (logic->rsp.paramCode) {
|
||||||
|
case ResponseMsgParamCodes::Processing:
|
||||||
|
logic->progressCode = static_cast<ProgressCode>(logic->rsp.paramValue);
|
||||||
|
logic->errorCode = ErrorCode::OK;
|
||||||
|
SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
|
||||||
|
break;
|
||||||
|
case ResponseMsgParamCodes::Error:
|
||||||
|
// in case of an error the progress code remains as it has been before
|
||||||
|
logic->errorCode = static_cast<ErrorCode>(logic->rsp.paramValue);
|
||||||
|
// keep on reporting the state of fsensor regularly even in command error state
|
||||||
|
// - the MMU checks FINDA and fsensor even while recovering from errors
|
||||||
|
SendAndUpdateFilamentSensor();
|
||||||
|
return CommandError;
|
||||||
|
case ResponseMsgParamCodes::Finished:
|
||||||
|
logic->progressCode = ProgressCode::OK;
|
||||||
|
state = State::Ready;
|
||||||
|
return Finished;
|
||||||
|
default:
|
||||||
|
return ProtocolError;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::FilamentSensorStateSent:{
|
||||||
|
auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
if (expmsg != MessageReady)
|
||||||
|
return expmsg;
|
||||||
|
SendFINDAQuery();
|
||||||
|
} break;
|
||||||
|
case State::FINDAReqSent:
|
||||||
|
return ProcessFINDAReqSent(Processing, State::Wait);
|
||||||
|
case State::ButtonSent:{
|
||||||
|
// button is never confirmed ... may be it should be
|
||||||
|
// auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
// if (expmsg != MessageReady)
|
||||||
|
// return expmsg;
|
||||||
|
SendQuery();
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
return ProtocolError;
|
||||||
|
}
|
||||||
|
return Processing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Idle::Restart() {
|
||||||
|
state = State::Ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus Idle::Step() {
|
||||||
|
switch (state) {
|
||||||
|
case State::Ready: // check timeout
|
||||||
|
if (logic->Elapsed(heartBeatPeriod)) {
|
||||||
|
logic->SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
|
||||||
|
state = State::QuerySent;
|
||||||
|
return Processing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::QuerySent: { // check UART
|
||||||
|
auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
|
||||||
|
if (expmsg != MessageReady)
|
||||||
|
return expmsg;
|
||||||
|
// If we are accidentally in Idle and we receive something like "T0 P1" - that means the communication dropped out while a command was in progress.
|
||||||
|
// That causes no issues here, we just need to switch to Command processing and continue there from now on.
|
||||||
|
// The usual response in this case should be some command and "F" - finished - that confirms we are in an Idle state even on the MMU side.
|
||||||
|
switch( logic->rsp.request.code ){
|
||||||
|
case RequestMsgCodes::Cut:
|
||||||
|
case RequestMsgCodes::Eject:
|
||||||
|
case RequestMsgCodes::Load:
|
||||||
|
case RequestMsgCodes::Mode:
|
||||||
|
case RequestMsgCodes::Tool:
|
||||||
|
case RequestMsgCodes::Unload:
|
||||||
|
if( logic->rsp.paramCode != ResponseMsgParamCodes::Finished ){
|
||||||
|
logic->SwitchFromIdleToCommand();
|
||||||
|
return Processing;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SendFINDAQuery();
|
||||||
|
return Processing;
|
||||||
|
} break;
|
||||||
|
case State::FINDAReqSent:
|
||||||
|
return ProcessFINDAReqSent(Finished, State::Ready);
|
||||||
|
default:
|
||||||
|
return ProtocolError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "return Finished" in this state machine requires a bit of explanation:
|
||||||
|
// The Idle state either did nothing (still waiting for the heartbeat timeout)
|
||||||
|
// or just successfully received the answer to Q0, whatever that was.
|
||||||
|
// In both cases, it is ready to hand over work to a command or something else,
|
||||||
|
// therefore we are returning Finished (also to exit mmu_loop() and unblock Marlin's loop!).
|
||||||
|
// If there is no work, we'll end up in the Idle state again
|
||||||
|
// and we'll send the heartbeat message after the specified timeout.
|
||||||
|
return Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
|
||||||
|
: stopped(this)
|
||||||
|
, startSeq(this)
|
||||||
|
, idle(this)
|
||||||
|
, command(this)
|
||||||
|
, currentState(&stopped)
|
||||||
|
, plannedRq(RequestMsgCodes::unknown, 0)
|
||||||
|
, lastUARTActivityMs(0)
|
||||||
|
, rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
|
||||||
|
, state(State::Stopped)
|
||||||
|
, lrb(0)
|
||||||
|
, uart(uart)
|
||||||
|
, lastFSensor((uint8_t)WhereIsFilament())
|
||||||
|
{}
|
||||||
|
|
||||||
|
void ProtocolLogic::Start() {
|
||||||
|
state = State::InitSequence;
|
||||||
|
currentState = &startSeq;
|
||||||
|
startSeq.Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::Stop() {
|
||||||
|
state = State::Stopped;
|
||||||
|
currentState = &stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::ToolChange(uint8_t slot) {
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Tool, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::UnloadFilament() {
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Unload, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::LoadFilament(uint8_t slot) {
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Load, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::EjectFilament(uint8_t slot) {
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::CutFilament(uint8_t slot){
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::ResetMMU() {
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::Button(uint8_t index){
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::Home(uint8_t mode){
|
||||||
|
PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
|
||||||
|
plannedRq = rq;
|
||||||
|
if( ! currentState->ExpectsResponse() ){
|
||||||
|
ActivatePlannedRequest();
|
||||||
|
} // otherwise wait for an empty window to activate the request
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMU2::ProtocolLogic::ActivatePlannedRequest(){
|
||||||
|
if( plannedRq.code == RequestMsgCodes::Button ){
|
||||||
|
// only issue the button to the MMU and do not restart the state machines
|
||||||
|
command.SendButton(plannedRq.value);
|
||||||
|
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||||
|
return true;
|
||||||
|
} else if( plannedRq.code != RequestMsgCodes::unknown ){
|
||||||
|
currentState = &command;
|
||||||
|
command.SetRequestMsg(plannedRq);
|
||||||
|
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||||
|
command.Restart();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::SwitchFromIdleToCommand(){
|
||||||
|
currentState = &command;
|
||||||
|
command.SetRequestMsg(rsp.request);
|
||||||
|
// we are recovering from a communication drop out, the command is already running
|
||||||
|
// and we have just received a response to a Q0 message about a command progress
|
||||||
|
command.ContinueFromIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::SwitchToIdle() {
|
||||||
|
state = State::Running;
|
||||||
|
currentState = &idle;
|
||||||
|
idle.Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::HandleCommunicationTimeout() {
|
||||||
|
uart->flush(); // clear the output buffer
|
||||||
|
currentState = &startSeq;
|
||||||
|
state = State::InitSequence;
|
||||||
|
startSeq.Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProtocolLogic::Elapsed(uint32_t timeout) const {
|
||||||
|
return _millis() >= (lastUARTActivityMs + timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::RecordUARTActivity() {
|
||||||
|
lastUARTActivityMs = _millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::RecordReceivedByte(uint8_t c){
|
||||||
|
lastReceivedBytes[lrb] = c;
|
||||||
|
lrb = (lrb+1) % lastReceivedBytes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
char NibbleToChar(uint8_t c){
|
||||||
|
switch (c) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
return c + '0';
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
case 14:
|
||||||
|
case 15:
|
||||||
|
return (c - 10) + 'a';
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::FormatLastReceivedBytes(char *dst){
|
||||||
|
for(uint8_t i = 0; i < lastReceivedBytes.size(); ++i){
|
||||||
|
uint8_t b = lastReceivedBytes[ (lrb-i-1) % lastReceivedBytes.size() ];
|
||||||
|
dst[i*3] = NibbleToChar(b >> 4);
|
||||||
|
dst[i*3+1] = NibbleToChar(b & 0xf);
|
||||||
|
dst[i*3+2] = ' ';
|
||||||
|
}
|
||||||
|
dst[ (lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst){
|
||||||
|
*dst++ = '<';
|
||||||
|
for(uint8_t i = 0; i < lrb; ++i){
|
||||||
|
uint8_t b = lastReceivedBytes[ i ];
|
||||||
|
if( b < 32 )b = '.';
|
||||||
|
if( b > 127 )b = '.';
|
||||||
|
*dst++ = b;
|
||||||
|
}
|
||||||
|
*dst = 0; // terminate properly
|
||||||
|
lrb = 0; // reset the input buffer index in case of a clean message
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size){
|
||||||
|
constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 2;
|
||||||
|
char tmp[rqs] = ">";
|
||||||
|
static char lastMsg[rqs] = "";
|
||||||
|
for(uint8_t i = 0; i < size; ++i){
|
||||||
|
uint8_t b = txbuff[i];
|
||||||
|
if( b < 32 )b = '.';
|
||||||
|
if( b > 127 )b = '.';
|
||||||
|
tmp[i+1] = b;
|
||||||
|
}
|
||||||
|
tmp[size+1] = '\n';
|
||||||
|
tmp[size+2] = 0;
|
||||||
|
if( !strncmp(tmp, ">S0.\n", rqs) && !strncmp(lastMsg, tmp, rqs) ){
|
||||||
|
// @@TODO we skip the repeated request msgs for now
|
||||||
|
// to avoid spoiling the whole log just with ">S0" messages
|
||||||
|
// especially when the MMU is not connected.
|
||||||
|
// We'll lose the ability to see if the printer is actually
|
||||||
|
// trying to find the MMU, but since it has been reliable in the past
|
||||||
|
// we can live without it for now.
|
||||||
|
} else {
|
||||||
|
MMU2_ECHO_MSG(tmp);
|
||||||
|
}
|
||||||
|
memcpy(lastMsg, tmp, rqs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMU2::ProtocolLogic::LogError(const char *reason){
|
||||||
|
char lrb[lastReceivedBytes.size() * 3];
|
||||||
|
FormatLastReceivedBytes(lrb);
|
||||||
|
|
||||||
|
MMU2_ERROR_MSG(reason);
|
||||||
|
SERIAL_ECHO(", last bytes: ");
|
||||||
|
SERIAL_ECHOLN(lrb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolLogic::LogResponse(){
|
||||||
|
char lrb[lastReceivedBytes.size()];
|
||||||
|
FormatLastResponseMsgAndClearLRB(lrb);
|
||||||
|
MMU2_ECHO_MSG(lrb);
|
||||||
|
SERIAL_ECHOLN();
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus MMU2::ProtocolLogic::HandleCommError(const char *msg, StepStatus ss){
|
||||||
|
protocol.ResetResponseDecoder();
|
||||||
|
HandleCommunicationTimeout();
|
||||||
|
if( dataTO.Record(ss) ){
|
||||||
|
LogError(msg);
|
||||||
|
return dataTO.InitialCause();
|
||||||
|
} else {
|
||||||
|
return Processing; // suppress short drop outs of communication
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StepStatus ProtocolLogic::Step() {
|
||||||
|
if( ! currentState->ExpectsResponse() ){ // if not waiting for a response, activate a planned request immediately
|
||||||
|
ActivatePlannedRequest();
|
||||||
|
}
|
||||||
|
auto currentStatus = currentState->Step();
|
||||||
|
switch (currentStatus) {
|
||||||
|
case Processing:
|
||||||
|
// we are ok, the state machine continues correctly
|
||||||
|
break;
|
||||||
|
case Finished: {
|
||||||
|
// We are ok, switching to Idle if there is no potential next request planned.
|
||||||
|
// But the trouble is we must report a finished command if the previous command has just been finished
|
||||||
|
// i.e. only try to find some planned command if we just finished the Idle cycle
|
||||||
|
bool previousCommandFinished = currentState == &command; // @@TODO this is a nasty hack :(
|
||||||
|
if( ! ActivatePlannedRequest() ){ // if nothing is planned, switch to Idle
|
||||||
|
SwitchToIdle();
|
||||||
|
} else {
|
||||||
|
// if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
|
||||||
|
if( ! previousCommandFinished && currentState == &command){
|
||||||
|
currentStatus = Processing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CommandRejected:
|
||||||
|
// we have to repeat it - that's the only thing we can do
|
||||||
|
// no change in state
|
||||||
|
// @@TODO wait until Q0 returns command in progress finished, then we can send this one
|
||||||
|
LogError("Command rejected");
|
||||||
|
command.Restart();
|
||||||
|
break;
|
||||||
|
case CommandError:
|
||||||
|
LogError("Command Error");
|
||||||
|
// we shall probably transfer into the Idle state and await further instructions from the upper layer
|
||||||
|
// Idle state may solve the problem of keeping up the heart beat running
|
||||||
|
break;
|
||||||
|
case VersionMismatch:
|
||||||
|
LogError("Version mismatch");
|
||||||
|
Stop(); // cannot continue
|
||||||
|
break;
|
||||||
|
case ProtocolError:
|
||||||
|
currentStatus = HandleCommError("Protocol error", ProtocolError);
|
||||||
|
break;
|
||||||
|
case CommunicationTimeout:
|
||||||
|
currentStatus = HandleCommError("Communication timeout", CommunicationTimeout);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return currentStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ProtocolLogic::CommandInProgress() const {
|
||||||
|
if( currentState != &command )
|
||||||
|
return 0;
|
||||||
|
return (uint8_t)command.ReqMsg().code;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DropOutFilter::Record(StepStatus ss){
|
||||||
|
if( occurrences == maxOccurrences ){
|
||||||
|
cause = ss;
|
||||||
|
}
|
||||||
|
--occurrences;
|
||||||
|
return occurrences == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,314 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
// #include <array> //@@TODO Don't we have STL for AVR somewhere?
|
||||||
|
template<typename T, uint8_t N>
|
||||||
|
class array {
|
||||||
|
T data[N];
|
||||||
|
public:
|
||||||
|
array() = default;
|
||||||
|
inline constexpr T* begin()const { return data; }
|
||||||
|
inline constexpr T* end()const { return data + N; }
|
||||||
|
constexpr uint8_t size()const { return N; }
|
||||||
|
inline T &operator[](uint8_t i){
|
||||||
|
return data[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "mmu2/error_codes.h"
|
||||||
|
#include "mmu2/progress_codes.h"
|
||||||
|
#include "mmu2_protocol.h"
|
||||||
|
|
||||||
|
#include "mmu2_serial.h"
|
||||||
|
|
||||||
|
/// New MMU2 protocol logic
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
using namespace modules::protocol;
|
||||||
|
|
||||||
|
class ProtocolLogic;
|
||||||
|
|
||||||
|
/// ProtocolLogic stepping statuses
|
||||||
|
enum StepStatus : uint_fast8_t {
|
||||||
|
Processing = 0,
|
||||||
|
MessageReady, ///< a message has been successfully decoded from the received bytes
|
||||||
|
Finished,
|
||||||
|
CommunicationTimeout, ///< the MMU failed to respond to a request within a specified time frame
|
||||||
|
ProtocolError, ///< bytes read from the MMU didn't form a valid response
|
||||||
|
CommandRejected, ///< the MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands)
|
||||||
|
CommandError, ///< the command in progress stopped due to unrecoverable error, user interaction required
|
||||||
|
VersionMismatch, ///< the MMU reports its firmware version incompatible with our implementation
|
||||||
|
CommunicationRecovered,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static constexpr uint32_t linkLayerTimeout = 2000; ///< default link layer communication timeout
|
||||||
|
static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout
|
||||||
|
static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of heart beat messages (Q0)
|
||||||
|
|
||||||
|
static_assert( heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
|
||||||
|
|
||||||
|
/// Base class for sub-automata of the ProtocolLogic class.
|
||||||
|
/// Their operation should never block (wait inside).
|
||||||
|
class ProtocolLogicPartBase {
|
||||||
|
public:
|
||||||
|
inline ProtocolLogicPartBase(ProtocolLogic *logic)
|
||||||
|
: logic(logic)
|
||||||
|
, state(State::Ready) {}
|
||||||
|
|
||||||
|
/// Restarts the sub-automaton
|
||||||
|
virtual void Restart() = 0;
|
||||||
|
|
||||||
|
/// Makes one step in the sub-automaton
|
||||||
|
/// @returns StepStatus
|
||||||
|
virtual StepStatus Step() = 0;
|
||||||
|
|
||||||
|
/// @returns true if the state machine is waiting for a response from the MMU
|
||||||
|
bool ExpectsResponse()const { return state != State::Ready && state != State::Wait; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ProtocolLogic *logic; ///< pointer to parent ProtocolLogic layer
|
||||||
|
friend class ProtocolLogic;
|
||||||
|
|
||||||
|
/// Common internal states of the derived sub-automata
|
||||||
|
/// General rule of thumb: *Sent states are waiting for a response from the MMU
|
||||||
|
enum class State : uint_fast8_t {
|
||||||
|
Ready,
|
||||||
|
Wait,
|
||||||
|
|
||||||
|
S0Sent,
|
||||||
|
S1Sent,
|
||||||
|
S2Sent,
|
||||||
|
QuerySent,
|
||||||
|
CommandSent,
|
||||||
|
FilamentSensorStateSent,
|
||||||
|
FINDAReqSent,
|
||||||
|
ButtonSent,
|
||||||
|
|
||||||
|
ContinueFromIdle
|
||||||
|
};
|
||||||
|
|
||||||
|
State state; ///< internal state of the sub-automaton
|
||||||
|
|
||||||
|
/// @returns the status of processing of the FINDA query response
|
||||||
|
/// @param finishedRV returned value in case the message was successfully received and processed
|
||||||
|
/// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
|
||||||
|
StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
|
||||||
|
|
||||||
|
/// Called repeatedly while waiting for a query (Q0) period.
|
||||||
|
/// All event checks to report immediately from the printer to the MMU shall be done in this method.
|
||||||
|
/// So far, the only such a case is the filament sensor, but there can be more like this in the future.
|
||||||
|
void CheckAndReportAsyncEvents();
|
||||||
|
|
||||||
|
void SendQuery();
|
||||||
|
|
||||||
|
void SendFINDAQuery();
|
||||||
|
|
||||||
|
void SendAndUpdateFilamentSensor();
|
||||||
|
|
||||||
|
void SendButton(uint8_t btn);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Starting sequence of the communication with the MMU.
|
||||||
|
/// The printer shall ask for MMU's version numbers.
|
||||||
|
/// If everything goes well and the MMU's version is good enough,
|
||||||
|
/// the ProtocolLogic layer may continue talking to the MMU
|
||||||
|
class StartSeq : public ProtocolLogicPartBase {
|
||||||
|
public:
|
||||||
|
inline StartSeq(ProtocolLogic *logic)
|
||||||
|
: ProtocolLogicPartBase(logic) {}
|
||||||
|
void Restart() override;
|
||||||
|
StepStatus Step() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A command and its lifecycle.
|
||||||
|
/// CommandSent:
|
||||||
|
/// - the command was placed into the UART TX buffer, awaiting response from the MMU
|
||||||
|
/// - if the MMU confirms the command, we'll wait for it to finish
|
||||||
|
/// - if the MMU refuses the command, we report an error (should normally not happen unless someone is hacking the communication without waiting for the previous command to finish)
|
||||||
|
/// Wait:
|
||||||
|
/// - waiting for the MMU to process the command - may take several seconds, for example Tool change operation
|
||||||
|
/// - meawhile, every 300ms we send a Q0 query to obtain the current state of the command being processed
|
||||||
|
/// - as soon as we receive a response to Q0 from the MMU, we process it in the next state
|
||||||
|
/// QuerySent - check the reply from the MMU - can be any of the following:
|
||||||
|
/// - Processing: the MMU is still working
|
||||||
|
/// - Error: the command failed on the MMU, we'll have the exact error report in the response message
|
||||||
|
/// - Finished: the MMU finished the command successfully, another command may be issued now
|
||||||
|
class Command : public ProtocolLogicPartBase {
|
||||||
|
public:
|
||||||
|
inline Command(ProtocolLogic *logic)
|
||||||
|
: ProtocolLogicPartBase(logic)
|
||||||
|
, rq(RequestMsgCodes::unknown, 0) {}
|
||||||
|
void Restart() override;
|
||||||
|
StepStatus Step() override;
|
||||||
|
inline void SetRequestMsg(RequestMsg msg) {
|
||||||
|
rq = msg;
|
||||||
|
}
|
||||||
|
void ContinueFromIdle(){
|
||||||
|
state = State::ContinueFromIdle;
|
||||||
|
}
|
||||||
|
inline const RequestMsg &ReqMsg()const { return rq; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RequestMsg rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Idle state - we have no command for the MMU, so we are only regularly querying its state with Q0 messages.
|
||||||
|
/// The idle state can be interrupted any time to issue a command into the MMU
|
||||||
|
class Idle : public ProtocolLogicPartBase {
|
||||||
|
public:
|
||||||
|
inline Idle(ProtocolLogic *logic)
|
||||||
|
: ProtocolLogicPartBase(logic) {}
|
||||||
|
void Restart() override;
|
||||||
|
StepStatus Step() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The communication with the MMU is stopped/disabled (for whatever reason).
|
||||||
|
/// Nothing is being put onto the UART.
|
||||||
|
class Stopped : public ProtocolLogicPartBase {
|
||||||
|
public:
|
||||||
|
inline Stopped(ProtocolLogic *logic)
|
||||||
|
: ProtocolLogicPartBase(logic) {}
|
||||||
|
void Restart() override {}
|
||||||
|
StepStatus Step() override { return Processing; }
|
||||||
|
};
|
||||||
|
|
||||||
|
///< Filter of short consecutive drop outs which are recovered instantly
|
||||||
|
class DropOutFilter {
|
||||||
|
StepStatus cause;
|
||||||
|
uint8_t occurrences;
|
||||||
|
public:
|
||||||
|
static constexpr uint8_t maxOccurrences = 3;
|
||||||
|
static_assert (maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards");
|
||||||
|
DropOutFilter() = default;
|
||||||
|
|
||||||
|
/// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
|
||||||
|
bool Record(StepStatus ss);
|
||||||
|
|
||||||
|
/// @returns the initial cause which started this drop out event
|
||||||
|
inline StepStatus InitialCause()const { return cause; }
|
||||||
|
|
||||||
|
/// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2)
|
||||||
|
inline void Reset(){ occurrences = maxOccurrences; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Logic layer of the MMU vs. printer communication protocol
|
||||||
|
class ProtocolLogic {
|
||||||
|
public:
|
||||||
|
ProtocolLogic(MMU2Serial *uart);
|
||||||
|
|
||||||
|
/// Start/Enable communication with the MMU
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
/// Stop/Disable communication with the MMU
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
// Issue commands to the MMU
|
||||||
|
void ToolChange(uint8_t slot);
|
||||||
|
void UnloadFilament();
|
||||||
|
void LoadFilament(uint8_t slot);
|
||||||
|
void EjectFilament(uint8_t slot);
|
||||||
|
void CutFilament(uint8_t slot);
|
||||||
|
void ResetMMU();
|
||||||
|
void Button(uint8_t index);
|
||||||
|
void Home(uint8_t mode);
|
||||||
|
|
||||||
|
/// Step the state machine
|
||||||
|
StepStatus Step();
|
||||||
|
|
||||||
|
/// @returns the current/latest error code as reported by the MMU
|
||||||
|
ErrorCode Error() const { return errorCode; }
|
||||||
|
|
||||||
|
/// @returns the current/latest process code as reported by the MMU
|
||||||
|
ProgressCode Progress() const { return progressCode; }
|
||||||
|
|
||||||
|
uint8_t CommandInProgress()const;
|
||||||
|
|
||||||
|
inline bool Running()const {
|
||||||
|
return state == State::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool FindaPressed() const {
|
||||||
|
return findaPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef UNITTEST
|
||||||
|
private:
|
||||||
|
#endif
|
||||||
|
|
||||||
|
StepStatus ProcessUARTByte(uint8_t c);
|
||||||
|
StepStatus ExpectingMessage(uint32_t timeout);
|
||||||
|
void SendMsg(RequestMsg rq);
|
||||||
|
void SwitchToIdle();
|
||||||
|
void HandleCommunicationTimeout();
|
||||||
|
StepStatus HandleCommError(const char *msg, StepStatus ss);
|
||||||
|
bool Elapsed(uint32_t timeout) const;
|
||||||
|
void RecordUARTActivity();
|
||||||
|
void RecordReceivedByte(uint8_t c);
|
||||||
|
void FormatLastReceivedBytes(char *dst);
|
||||||
|
void FormatLastResponseMsgAndClearLRB(char *dst);
|
||||||
|
void LogRequestMsg(const uint8_t *txbuff, uint8_t size);
|
||||||
|
void LogError(const char *reason);
|
||||||
|
void LogResponse();
|
||||||
|
void SwitchFromIdleToCommand();
|
||||||
|
|
||||||
|
enum class State : uint_fast8_t {
|
||||||
|
Stopped, ///< stopped for whatever reason
|
||||||
|
InitSequence, ///< initial sequence running
|
||||||
|
Running ///< normal operation - Idle + Command processing
|
||||||
|
};
|
||||||
|
|
||||||
|
// individual sub-state machines - may be they can be combined into a union since only one is active at once
|
||||||
|
Stopped stopped;
|
||||||
|
StartSeq startSeq;
|
||||||
|
Idle idle;
|
||||||
|
Command command;
|
||||||
|
ProtocolLogicPartBase *currentState; ///< command currently being processed
|
||||||
|
|
||||||
|
/// Records the next planned state, "unknown" msg code if no command is planned.
|
||||||
|
/// This is not intended to be a queue of commands to process, protocol_logic must not queue commands.
|
||||||
|
/// It exists solely to prevent breaking the Request-Response protocol handshake -
|
||||||
|
/// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that
|
||||||
|
/// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0.
|
||||||
|
///
|
||||||
|
/// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes,
|
||||||
|
/// these variables will get overwritten by the last call.
|
||||||
|
/// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish,
|
||||||
|
/// which includes all responses (and error recovery if any).
|
||||||
|
RequestMsg plannedRq;
|
||||||
|
|
||||||
|
/// Plan a command to be processed once the immediate response to a sent request arrives
|
||||||
|
void PlanGenericRequest(RequestMsg rq);
|
||||||
|
/// Activate the planned state once the immediate response to a sent request arrived
|
||||||
|
bool ActivatePlannedRequest();
|
||||||
|
|
||||||
|
uint32_t lastUARTActivityMs; ///< timestamp - last ms when something occurred on the UART
|
||||||
|
DropOutFilter dataTO; ///< Filter of short consecutive drop outs which are recovered instantly
|
||||||
|
|
||||||
|
ResponseMsg rsp; ///< decoded response message from the MMU protocol
|
||||||
|
|
||||||
|
State state; ///< internal state of ProtocolLogic
|
||||||
|
|
||||||
|
Protocol protocol; ///< protocol codec
|
||||||
|
|
||||||
|
array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
|
||||||
|
uint8_t lrb;
|
||||||
|
|
||||||
|
MMU2Serial *uart; ///< UART interface
|
||||||
|
|
||||||
|
ErrorCode errorCode; ///< last received error code from the MMU
|
||||||
|
ProgressCode progressCode; ///< last received progress code from the MMU
|
||||||
|
|
||||||
|
uint8_t lastFSensor; ///< last state of filament sensor
|
||||||
|
|
||||||
|
bool findaPressed;
|
||||||
|
|
||||||
|
friend class ProtocolLogicPartBase;
|
||||||
|
friend class Stopped;
|
||||||
|
friend class Command;
|
||||||
|
friend class Idle;
|
||||||
|
friend class StartSeq;
|
||||||
|
|
||||||
|
friend class MMU2;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#include "mmu2_reporting.h"
|
||||||
|
|
||||||
|
// @@TODO implement the interface for MK3
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
void BeginReport(CommandInProgress cip, uint16_t ec) { }
|
||||||
|
|
||||||
|
void EndReport(CommandInProgress cip, uint16_t ec) { }
|
||||||
|
|
||||||
|
void ReportErrorHook(CommandInProgress cip, uint16_t ec) { }
|
||||||
|
|
||||||
|
void ReportProgressHook(CommandInProgress cip, uint16_t ec) { }
|
||||||
|
|
||||||
|
Buttons ButtonPressed(uint16_t ec) { }
|
||||||
|
|
||||||
|
bool MMUAvailable() { }
|
||||||
|
|
||||||
|
bool UseMMU() { }
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
/// @file mmu2_reporting.h
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
enum CommandInProgress : uint8_t {
|
||||||
|
NoCommand = 0,
|
||||||
|
CutFilament = 'C',
|
||||||
|
EjectFilament = 'E',
|
||||||
|
Homing = 'H',
|
||||||
|
LoadFilament = 'L',
|
||||||
|
Reset = 'X',
|
||||||
|
ToolChange = 'T',
|
||||||
|
UnloadFilament = 'U',
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Called at the begin of every MMU operation
|
||||||
|
void BeginReport(CommandInProgress cip, uint16_t ec);
|
||||||
|
|
||||||
|
/// Called at the end of every MMU operation
|
||||||
|
void EndReport(CommandInProgress cip, uint16_t ec);
|
||||||
|
|
||||||
|
/// Called when the MMU sends operation error (even repeatedly)
|
||||||
|
void ReportErrorHook(CommandInProgress cip, uint16_t ec);
|
||||||
|
|
||||||
|
/// Called when the MMU sends operation progress update
|
||||||
|
void ReportProgressHook(CommandInProgress cip, uint16_t ec);
|
||||||
|
|
||||||
|
/// Button codes + extended actions performed on the printer's side
|
||||||
|
enum Buttons : uint8_t {
|
||||||
|
Left = 0,
|
||||||
|
Middle,
|
||||||
|
Right,
|
||||||
|
|
||||||
|
// performed on the printer's side
|
||||||
|
RestartMMU,
|
||||||
|
StopPrint,
|
||||||
|
|
||||||
|
NoButton = 0xff // shall be kept last
|
||||||
|
};
|
||||||
|
|
||||||
|
Buttons ButtonPressed(uint16_t ec);
|
||||||
|
|
||||||
|
/// @returns true if the MMU is communicating and available
|
||||||
|
/// can change at runtime
|
||||||
|
bool MMUAvailable();
|
||||||
|
|
||||||
|
/// Global Enable/Disable use MMU (to be stored in EEPROM)
|
||||||
|
bool UseMMU();
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include "mmu2_serial.h"
|
||||||
|
|
||||||
|
//@@TODO implement for MK3
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
void MMU2Serial::begin(uint32_t baud){ }
|
||||||
|
void MMU2Serial::close() { }
|
||||||
|
int MMU2Serial::read() { }
|
||||||
|
void MMU2Serial::flush() { }
|
||||||
|
size_t MMU2Serial::write(const uint8_t *buffer, size_t size) { }
|
||||||
|
|
||||||
|
MMU2Serial mmu2Serial;
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
namespace MMU2 {
|
||||||
|
|
||||||
|
/// A minimal serial interface for the MMU
|
||||||
|
class MMU2Serial {
|
||||||
|
public:
|
||||||
|
MMU2Serial() = default;
|
||||||
|
// bool available()const;
|
||||||
|
void begin(uint32_t baud);
|
||||||
|
void close();
|
||||||
|
int read();
|
||||||
|
void flush();
|
||||||
|
size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MMU2Serial mmu2Serial;
|
||||||
|
|
||||||
|
} // namespace MMU2
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
#include "Filament_sensor.h"
|
#include "Filament_sensor.h"
|
||||||
|
|
||||||
#include "mmu.h"
|
#include "mmu2.h"
|
||||||
#include "ConfigurationStore.h"
|
#include "ConfigurationStore.h"
|
||||||
|
|
||||||
#include "Prusa_farm.h"
|
#include "Prusa_farm.h"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
constexpr inline int strlen_constexpr(const char* str){
|
||||||
|
return *str ? 1 + strlen_constexpr(str + 1) : 0;
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
|
||||||
#include "mmu.h"
|
#include "mmu2.h"
|
||||||
|
|
||||||
#include "static_assert.h"
|
#include "static_assert.h"
|
||||||
#include "first_lay_cal.h"
|
#include "first_lay_cal.h"
|
||||||
|
|
@ -450,19 +450,20 @@ void lcdui_print_percent_done(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print extruder status (5 chars total)
|
// Print extruder status (5 chars total)
|
||||||
void lcdui_print_extruder(void)
|
void lcdui_print_extruder(void) {
|
||||||
{
|
uint8_t chars = 0;
|
||||||
int chars = 0;
|
// @@TODO if (MMU2::mmu2.get_current_tool() == tmp_extruder) {
|
||||||
if (mmu_extruder == tmp_extruder) {
|
// if (MMU2::mmu2.get_current_tool() == MMU2::FILAMENT_UNKNOWN)
|
||||||
if (mmu_extruder == MMU_FILAMENT_UNKNOWN) chars = lcd_printf_P(_N(" F?"));
|
// chars = lcd_printf_P(_N(" F?"));
|
||||||
else chars = lcd_printf_P(_N(" F%u"), mmu_extruder + 1);
|
// else
|
||||||
}
|
// chars = lcd_printf_P(_N(" F%u"), MMU2::mmu2.get_current_tool() + 1);
|
||||||
else
|
// } else {
|
||||||
{
|
// if (MMU2::mmu2.get_current_tool() == MMU2::FILAMENT_UNKNOWN)
|
||||||
if (mmu_extruder == MMU_FILAMENT_UNKNOWN) chars = lcd_printf_P(_N(" ?>%u"), tmp_extruder + 1);
|
// chars = lcd_printf_P(_N(" ?>%u"), tmp_extruder + 1);
|
||||||
else chars = lcd_printf_P(_N(" %u>%u"), mmu_extruder + 1, tmp_extruder + 1);
|
// else
|
||||||
}
|
// chars = lcd_printf_P(_N(" %u>%u"), MMU2::mmu2.get_current_tool() + 1, tmp_extruder + 1);
|
||||||
lcd_space(5 - chars);
|
// }
|
||||||
|
lcd_space(5 - chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print farm number (5 chars total)
|
// Print farm number (5 chars total)
|
||||||
|
|
@ -719,7 +720,7 @@ void lcdui_print_status_screen(void)
|
||||||
//Print SD status (7 chars)
|
//Print SD status (7 chars)
|
||||||
lcdui_print_percent_done();
|
lcdui_print_percent_done();
|
||||||
|
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
//Print extruder status (5 chars)
|
//Print extruder status (5 chars)
|
||||||
lcdui_print_extruder();
|
lcdui_print_extruder();
|
||||||
else if (farm_mode)
|
else if (farm_mode)
|
||||||
|
|
@ -945,7 +946,7 @@ void lcd_commands()
|
||||||
enquecommand_P(PSTR("M140 S0")); // turn off heatbed
|
enquecommand_P(PSTR("M140 S0")); // turn off heatbed
|
||||||
enquecommand_P(PSTR("G1 Z10 F1300.000")); //lift Z
|
enquecommand_P(PSTR("G1 Z10 F1300.000")); //lift Z
|
||||||
enquecommand_P(PSTR("G1 X10 Y180 F4000")); //Go to parking position
|
enquecommand_P(PSTR("G1 X10 Y180 F4000")); //Go to parking position
|
||||||
if (mmu_enabled) enquecommand_P(PSTR("M702 C")); //unload from nozzle
|
if (MMU2::mmu2.Enabled()) enquecommand_P(PSTR("M702 C")); //unload from nozzle
|
||||||
enquecommand_P(PSTR("M84"));// disable motors
|
enquecommand_P(PSTR("M84"));// disable motors
|
||||||
forceMenuExpire = true; //if user dont confirm live adjust Z value by pressing the knob, we are saving last value by timeout to status screen
|
forceMenuExpire = true; //if user dont confirm live adjust Z value by pressing the knob, we are saving last value by timeout to status screen
|
||||||
lcd_commands_step = 1;
|
lcd_commands_step = 1;
|
||||||
|
|
@ -1188,14 +1189,14 @@ static void lcd_menu_fails_stats_mmu_print()
|
||||||
//! @todo Positioning of the messages and values on LCD aren't fixed to their exact place. This causes issues with translations.
|
//! @todo Positioning of the messages and values on LCD aren't fixed to their exact place. This causes issues with translations.
|
||||||
static void lcd_menu_fails_stats_mmu_total()
|
static void lcd_menu_fails_stats_mmu_total()
|
||||||
{
|
{
|
||||||
mmu_command(MmuCmd::S3);
|
// @@TODO mmu_command(MmuCmd::S3);
|
||||||
lcd_timeoutToStatus.stop(); //infinite timeout
|
lcd_timeoutToStatus.stop(); //infinite timeout
|
||||||
lcd_home();
|
lcd_home();
|
||||||
lcd_printf_P(PSTR("%S\n" " %-16.16S%-3d\n" " %-16.16S%-3d\n" " %-16.16S%-3d"),
|
// lcd_printf_P(PSTR("%S\n" " %-16.16S%-3d\n" " %-16.16S%-3d\n" " %-16.16S%-3d"),
|
||||||
_T(MSG_TOTAL_FAILURES),
|
// _T(MSG_TOTAL_FAILURES),
|
||||||
_T(MSG_MMU_FAILS), clamp999( eeprom_read_word((uint16_t*)EEPROM_MMU_FAIL_TOT) ),
|
// _T(MSG_MMU_FAILS), clamp999( eeprom_read_word((uint16_t*)EEPROM_MMU_FAIL_TOT) ),
|
||||||
_T(MSG_MMU_LOAD_FAILS), clamp999( eeprom_read_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT) ),
|
// _T(MSG_MMU_LOAD_FAILS), clamp999( eeprom_read_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT) ),
|
||||||
_i("MMU power fails"), clamp999( mmu_power_failures )); ////MSG_MMU_POWER_FAILS c=15
|
// _i("MMU power fails"), clamp999( mmu_power_failures )); ////MSG_MMU_POWER_FAILS c=15
|
||||||
menu_back_if_clicked_fb();
|
menu_back_if_clicked_fb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1680,13 +1681,15 @@ static void lcd_support_menu()
|
||||||
#endif // IR_SENSOR_ANALOG
|
#endif // IR_SENSOR_ANALOG
|
||||||
|
|
||||||
MENU_ITEM_BACK_P(STR_SEPARATOR);
|
MENU_ITEM_BACK_P(STR_SEPARATOR);
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
MENU_ITEM_BACK_P(_i("MMU2 connected")); ////MSG_MMU_CONNECTED c=18
|
MENU_ITEM_BACK_P(_i("MMU2 connected")); ////MSG_MMU_CONNECTED c=18
|
||||||
MENU_ITEM_BACK_P(PSTR(" FW:")); ////c=17
|
MENU_ITEM_BACK_P(PSTR(" FW:")); ////c=17
|
||||||
if (((menu_item - 1) == menu_line) && lcd_draw_update)
|
if (((menu_item - 1) == menu_line) && lcd_draw_update)
|
||||||
{
|
{
|
||||||
lcd_set_cursor(6, menu_row);
|
lcd_set_cursor(6, menu_row);
|
||||||
|
uint8_t mmu_version = 200; // @@TODO
|
||||||
|
uint8_t mmu_buildnr = 0;
|
||||||
if ((mmu_version > 0) && (mmu_buildnr > 0))
|
if ((mmu_version > 0) && (mmu_buildnr > 0))
|
||||||
lcd_printf_P(PSTR("%d.%d.%d-%d"), mmu_version/100, mmu_version%100/10, mmu_version%10, mmu_buildnr);
|
lcd_printf_P(PSTR("%d.%d.%d-%d"), mmu_version/100, mmu_version%100/10, mmu_version%10, mmu_buildnr);
|
||||||
else
|
else
|
||||||
|
|
@ -1919,7 +1922,7 @@ void mFilamentItem(uint16_t nTemp, uint16_t nTempBed)
|
||||||
nLevel = bFilamentPreheatState ? 1 : 2;
|
nLevel = bFilamentPreheatState ? 1 : 2;
|
||||||
bFilamentAction = true;
|
bFilamentAction = true;
|
||||||
menu_back(nLevel);
|
menu_back(nLevel);
|
||||||
extr_unload();
|
MMU2::mmu2.unload();
|
||||||
break;
|
break;
|
||||||
case FilamentAction::MmuEject:
|
case FilamentAction::MmuEject:
|
||||||
nLevel = bFilamentPreheatState ? 1 : 2;
|
nLevel = bFilamentPreheatState ? 1 : 2;
|
||||||
|
|
@ -3430,15 +3433,15 @@ static void lcd_show_sensors_state()
|
||||||
uint8_t idler_state = STATE_NA;
|
uint8_t idler_state = STATE_NA;
|
||||||
|
|
||||||
pinda_state = READ(Z_MIN_PIN);
|
pinda_state = READ(Z_MIN_PIN);
|
||||||
if (mmu_enabled && !mmu_last_finda_response.expired(1000))
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
finda_state = mmu_finda;
|
finda_state = MMU2::mmu2.FindaDetectsFilament();
|
||||||
}
|
}
|
||||||
lcd_puts_at_P(0, 0, MSG_PINDA);
|
lcd_puts_at_P(0, 0, MSG_PINDA);
|
||||||
lcd_set_cursor(LCD_WIDTH - 14, 0);
|
lcd_set_cursor(LCD_WIDTH - 14, 0);
|
||||||
lcd_print_state(pinda_state);
|
lcd_print_state(pinda_state);
|
||||||
|
|
||||||
if (mmu_enabled == true)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
lcd_puts_at_P(10, 0, _n("FINDA"));////MSG_FINDA c=5
|
lcd_puts_at_P(10, 0, _n("FINDA"));////MSG_FINDA c=5
|
||||||
lcd_set_cursor(LCD_WIDTH - 3, 0);
|
lcd_set_cursor(LCD_WIDTH - 3, 0);
|
||||||
|
|
@ -3775,7 +3778,7 @@ void lcd_first_layer_calibration_reset()
|
||||||
|
|
||||||
void lcd_v2_calibration()
|
void lcd_v2_calibration()
|
||||||
{
|
{
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
const uint8_t filament = choose_menu_P(
|
const uint8_t filament = choose_menu_P(
|
||||||
_T(MSG_SELECT_FILAMENT),
|
_T(MSG_SELECT_FILAMENT),
|
||||||
|
|
@ -3882,22 +3885,19 @@ static void wait_preheat()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lcd_wizard_load()
|
static void lcd_wizard_load() {
|
||||||
{
|
if (MMU2::mmu2.Enabled()) {
|
||||||
if (mmu_enabled)
|
lcd_show_fullscreen_message_and_wait_P(
|
||||||
{
|
_i("Please insert filament into the first tube of the MMU, then press the knob to load it.")); ////MSG_MMU_INSERT_FILAMENT_FIRST_TUBE c=20 r=6
|
||||||
lcd_show_fullscreen_message_and_wait_P(_i("Please insert filament into the first tube of the MMU, then press the knob to load it."));////MSG_MMU_INSERT_FILAMENT_FIRST_TUBE c=20 r=6
|
} else {
|
||||||
tmp_extruder = 0;
|
lcd_show_fullscreen_message_and_wait_P(
|
||||||
}
|
_i("Please insert filament into the extruder, then press the knob to load it.")); ////MSG_WIZARD_LOAD_FILAMENT c=20 r=6
|
||||||
else
|
}
|
||||||
{
|
lcd_update_enable(false);
|
||||||
lcd_show_fullscreen_message_and_wait_P(_i("Please insert filament into the extruder, then press the knob to load it."));////MSG_WIZARD_LOAD_FILAMENT c=20 r=6
|
lcd_clear();
|
||||||
}
|
lcd_puts_at_P(0, 2, _T(MSG_LOADING_FILAMENT));
|
||||||
lcd_update_enable(false);
|
loading_flag = true;
|
||||||
lcd_clear();
|
gcode_M701(0);
|
||||||
lcd_puts_at_P(0, 2, _T(MSG_LOADING_FILAMENT));
|
|
||||||
loading_flag = true;
|
|
||||||
gcode_M701();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lcd_autoDepleteEnabled()
|
bool lcd_autoDepleteEnabled()
|
||||||
|
|
@ -3913,7 +3913,7 @@ static void wizard_lay1cal_message(bool cold)
|
||||||
{
|
{
|
||||||
lcd_show_fullscreen_message_and_wait_P(
|
lcd_show_fullscreen_message_and_wait_P(
|
||||||
_i("Now I will calibrate distance between tip of the nozzle and heatbed surface.")); ////MSG_WIZARD_V2_CAL c=20 r=8
|
_i("Now I will calibrate distance between tip of the nozzle and heatbed surface.")); ////MSG_WIZARD_V2_CAL c=20 r=8
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
lcd_show_fullscreen_message_and_wait_P(
|
lcd_show_fullscreen_message_and_wait_P(
|
||||||
_i("Select a filament for the First Layer Calibration and select it in the on-screen menu."));////MSG_SELECT_FIL_1ST_LAYERCAL c=20 r=7
|
_i("Select a filament for the First Layer Calibration and select it in the on-screen menu."));////MSG_SELECT_FIL_1ST_LAYERCAL c=20 r=7
|
||||||
|
|
@ -4056,7 +4056,7 @@ void lcd_wizard(WizState state)
|
||||||
//start to preheat nozzle and bed to save some time later
|
//start to preheat nozzle and bed to save some time later
|
||||||
setTargetHotend(PLA_PREHEAT_HOTEND_TEMP, 0);
|
setTargetHotend(PLA_PREHEAT_HOTEND_TEMP, 0);
|
||||||
setTargetBed(PLA_PREHEAT_HPB_TEMP);
|
setTargetBed(PLA_PREHEAT_HPB_TEMP);
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
wizard_event = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_FILAMENT_LOADED), true);
|
wizard_event = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_FILAMENT_LOADED), true);
|
||||||
} else
|
} else
|
||||||
|
|
@ -4066,7 +4066,7 @@ void lcd_wizard(WizState state)
|
||||||
if (wizard_event) state = S::Lay1CalCold;
|
if (wizard_event) state = S::Lay1CalCold;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(mmu_enabled) state = S::LoadFilCold;
|
if(MMU2::mmu2.Enabled()) state = S::LoadFilCold;
|
||||||
else state = S::Preheat;
|
else state = S::Preheat;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -4256,7 +4256,7 @@ static void auto_deplete_switch()
|
||||||
|
|
||||||
static void settingsAutoDeplete()
|
static void settingsAutoDeplete()
|
||||||
{
|
{
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
#ifdef FILAMENT_SENSOR
|
#ifdef FILAMENT_SENSOR
|
||||||
if (fsensor.isError()) {
|
if (fsensor.isError()) {
|
||||||
|
|
@ -4280,7 +4280,7 @@ while(0)\
|
||||||
#ifdef MMU_HAS_CUTTER
|
#ifdef MMU_HAS_CUTTER
|
||||||
static void settingsCutter()
|
static void settingsCutter()
|
||||||
{
|
{
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
if (EEPROM_MMU_CUTTER_ENABLED_enabled == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED))
|
if (EEPROM_MMU_CUTTER_ENABLED_enabled == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED))
|
||||||
{
|
{
|
||||||
|
|
@ -4360,7 +4360,7 @@ while (0)
|
||||||
#define SETTINGS_MMU_MODE \
|
#define SETTINGS_MMU_MODE \
|
||||||
do\
|
do\
|
||||||
{\
|
{\
|
||||||
if (mmu_enabled)\
|
if (MMU2::mmu2.Enabled())\
|
||||||
{\
|
{\
|
||||||
if (SilentModeMenu_MMU == 0) MENU_ITEM_TOGGLE_P(_T(MSG_MMU_MODE), _T(MSG_NORMAL), lcd_silent_mode_mmu_set);\
|
if (SilentModeMenu_MMU == 0) MENU_ITEM_TOGGLE_P(_T(MSG_MMU_MODE), _T(MSG_NORMAL), lcd_silent_mode_mmu_set);\
|
||||||
else MENU_ITEM_TOGGLE_P(_T(MSG_MMU_MODE), _T(MSG_STEALTH), lcd_silent_mode_mmu_set);\
|
else MENU_ITEM_TOGGLE_P(_T(MSG_MMU_MODE), _T(MSG_STEALTH), lcd_silent_mode_mmu_set);\
|
||||||
|
|
@ -4716,7 +4716,7 @@ void lcd_hw_setup_menu(void) // can not be "static"
|
||||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||||
//! Fsensor Detection isn't ready for mmu yet it is temporarily disabled.
|
//! Fsensor Detection isn't ready for mmu yet it is temporarily disabled.
|
||||||
//! @todo Don't forget to remove this as soon Fsensor Detection works with mmu
|
//! @todo Don't forget to remove this as soon Fsensor Detection works with mmu
|
||||||
if(!mmu_enabled) MENU_ITEM_FUNCTION_P(PSTR("Fsensor Detection"), lcd_detect_IRsensor);
|
if(!MMU2::mmu2.Enabled()) MENU_ITEM_FUNCTION_P(PSTR("Fsensor Detection"), lcd_detect_IRsensor);
|
||||||
#endif //IR_SENSOR_ANALOG
|
#endif //IR_SENSOR_ANALOG
|
||||||
|
|
||||||
if (_md->experimental_menu_visibility)
|
if (_md->experimental_menu_visibility)
|
||||||
|
|
@ -4878,7 +4878,7 @@ static void lcd_calibration_menu()
|
||||||
//!
|
//!
|
||||||
//! Create list of items with header. Header can not be selected.
|
//! Create list of items with header. Header can not be selected.
|
||||||
//! Each item has text description passed by function parameter and
|
//! Each item has text description passed by function parameter and
|
||||||
//! number. There are 5 numbered items, if mmu_enabled, 4 otherwise.
|
//! number. There are 5 numbered items, if MMU2::mmu2.Enabled(), 4 otherwise.
|
||||||
//! Items are numbered from 1 to 4 or 5. But index returned starts at 0.
|
//! Items are numbered from 1 to 4 or 5. But index returned starts at 0.
|
||||||
//! There can be last item with different text and no number.
|
//! There can be last item with different text and no number.
|
||||||
//!
|
//!
|
||||||
|
|
@ -4889,7 +4889,7 @@ static void lcd_calibration_menu()
|
||||||
uint8_t choose_menu_P(const char *header, const char *item, const char *last_item)
|
uint8_t choose_menu_P(const char *header, const char *item, const char *last_item)
|
||||||
{
|
{
|
||||||
//following code should handle 3 to 127 number of items well
|
//following code should handle 3 to 127 number of items well
|
||||||
const int8_t items_no = last_item?(mmu_enabled?6:5):(mmu_enabled?5:4);
|
const int8_t items_no = last_item?(MMU2::mmu2.Enabled()?6:5):(MMU2::mmu2.Enabled()?5:4);
|
||||||
const uint8_t item_len = item?strlen_P(item):0;
|
const uint8_t item_len = item?strlen_P(item):0;
|
||||||
int8_t first = 0;
|
int8_t first = 0;
|
||||||
int8_t enc_dif = lcd_encoder_diff;
|
int8_t enc_dif = lcd_encoder_diff;
|
||||||
|
|
@ -5061,68 +5061,71 @@ static void lcd_disable_farm_mode()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void load_all_wrapper(){
|
||||||
|
for(uint8_t i = 0; i < 5; ++i){
|
||||||
|
MMU2::mmu2.load_filament(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void load_filament_wrapper(uint8_t i){
|
||||||
|
MMU2::mmu2.load_filament(i);
|
||||||
|
}
|
||||||
|
|
||||||
static void mmu_load_filament_menu()
|
static void mmu_load_filament_menu() {
|
||||||
{
|
|
||||||
MENU_BEGIN();
|
MENU_BEGIN();
|
||||||
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
||||||
MENU_ITEM_FUNCTION_P(_i("Load all"), load_all); ////MSG_LOAD_ALL c=18
|
MENU_ITEM_FUNCTION_P(_i("Load all"), load_all_wrapper); ////MSG_LOAD_ALL c=18
|
||||||
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
||||||
MENU_ITEM_FUNCTION_NR_P(_T(MSG_LOAD_FILAMENT), i + '1', extr_adj, i); ////MSG_LOAD_FILAMENT c=16
|
MENU_ITEM_FUNCTION_NR_P(_T(MSG_LOAD_FILAMENT), i + '1', load_filament_wrapper, i); ////MSG_LOAD_FILAMENT c=16
|
||||||
MENU_END();
|
MENU_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mmu_load_to_nozzle_menu()
|
static inline void lcd_mmu_load_to_nozzle_wrapper(uint8_t index){
|
||||||
{
|
MMU2::mmu2.load_filament_to_nozzle(index);
|
||||||
if (bFilamentAction)
|
}
|
||||||
{
|
|
||||||
|
static void mmu_load_to_nozzle_menu() {
|
||||||
|
if (bFilamentAction) {
|
||||||
MENU_BEGIN();
|
MENU_BEGIN();
|
||||||
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
||||||
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
||||||
MENU_ITEM_FUNCTION_NR_P(_T(MSG_LOAD_FILAMENT), i + '1', lcd_mmu_load_to_nozzle, i); ////MSG_LOAD_FILAMENT c=16
|
MENU_ITEM_FUNCTION_NR_P(_T(MSG_LOAD_FILAMENT), i + '1', lcd_mmu_load_to_nozzle_wrapper, i); ////MSG_LOAD_FILAMENT c=16
|
||||||
MENU_END();
|
MENU_END();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
eFilamentAction = FilamentAction::MmuLoad;
|
eFilamentAction = FilamentAction::MmuLoad;
|
||||||
preheat_or_continue();
|
preheat_or_continue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mmu_eject_filament(uint8_t filament)
|
static void mmu_eject_filament(uint8_t filament) {
|
||||||
{
|
|
||||||
menu_back();
|
menu_back();
|
||||||
mmu_eject_filament(filament, true);
|
MMU2::mmu2.eject_filament(filament, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mmu_fil_eject_menu()
|
static void mmu_fil_eject_menu() {
|
||||||
{
|
if (bFilamentAction) {
|
||||||
if (bFilamentAction)
|
|
||||||
{
|
|
||||||
MENU_BEGIN();
|
MENU_BEGIN();
|
||||||
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
||||||
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
||||||
MENU_ITEM_FUNCTION_NR_P(_T(MSG_EJECT_FILAMENT), i + '1', mmu_eject_filament, i); ////MSG_EJECT_FILAMENT c=16
|
MENU_ITEM_FUNCTION_NR_P(_T(MSG_EJECT_FILAMENT), i + '1', mmu_eject_filament, i); ////MSG_EJECT_FILAMENT c=16
|
||||||
MENU_END();
|
MENU_END();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
eFilamentAction = FilamentAction::MmuEject;
|
eFilamentAction = FilamentAction::MmuEject;
|
||||||
preheat_or_continue();
|
preheat_or_continue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef MMU_HAS_CUTTER
|
#ifdef MMU_HAS_CUTTER
|
||||||
|
static inline void mmu_cut_filament_wrapper(uint8_t index){
|
||||||
|
MMU2::mmu2.cut_filament(index);
|
||||||
|
}
|
||||||
|
|
||||||
static void mmu_cut_filament_menu()
|
static void mmu_cut_filament_menu() {
|
||||||
{
|
if (bFilamentAction) {
|
||||||
if(bFilamentAction)
|
|
||||||
{
|
|
||||||
MENU_BEGIN();
|
MENU_BEGIN();
|
||||||
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
MENU_ITEM_BACK_P(_T(MSG_MAIN));
|
||||||
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
for (uint8_t i = 0; i < MMU_FILAMENT_COUNT; i++)
|
||||||
MENU_ITEM_FUNCTION_NR_P(_T(MSG_CUT_FILAMENT), i + '1', mmu_cut_filament, i); ////MSG_CUT_FILAMENT c=16
|
MENU_ITEM_FUNCTION_NR_P(_T(MSG_CUT_FILAMENT), i + '1', mmu_cut_filament_wrapper, i); ////MSG_CUT_FILAMENT c=16
|
||||||
MENU_END();
|
MENU_END();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -5522,7 +5525,7 @@ static void lcd_main_menu()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! ( IS_SD_PRINTING || usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal) ) ) {
|
if ( ! ( IS_SD_PRINTING || usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal) ) ) {
|
||||||
if (mmu_enabled) {
|
if (MMU2::mmu2.Enabled()) {
|
||||||
MENU_ITEM_SUBMENU_P(_T(MSG_LOAD_FILAMENT), mmu_load_filament_menu);
|
MENU_ITEM_SUBMENU_P(_T(MSG_LOAD_FILAMENT), mmu_load_filament_menu);
|
||||||
MENU_ITEM_SUBMENU_P(_i("Load to nozzle"), mmu_load_to_nozzle_menu);////MSG_LOAD_TO_NOZZLE c=18
|
MENU_ITEM_SUBMENU_P(_i("Load to nozzle"), mmu_load_to_nozzle_menu);////MSG_LOAD_TO_NOZZLE c=18
|
||||||
MENU_ITEM_SUBMENU_P(_T(MSG_UNLOAD_FILAMENT), mmu_unload_filament);
|
MENU_ITEM_SUBMENU_P(_T(MSG_UNLOAD_FILAMENT), mmu_unload_filament);
|
||||||
|
|
@ -5532,7 +5535,7 @@ static void lcd_main_menu()
|
||||||
#endif //MMU_HAS_CUTTER
|
#endif //MMU_HAS_CUTTER
|
||||||
} else {
|
} else {
|
||||||
#ifdef FILAMENT_SENSOR
|
#ifdef FILAMENT_SENSOR
|
||||||
if (fsensor.getAutoLoadEnabled() && (mmu_enabled == false)) {
|
if (fsensor.getAutoLoadEnabled() && (MMU2::mmu2.Enabled() == false)) {
|
||||||
MENU_ITEM_SUBMENU_P(_i("AutoLoad filament"), lcd_menu_AutoLoadFilament);////MSG_AUTOLOAD_FILAMENT c=18
|
MENU_ITEM_SUBMENU_P(_i("AutoLoad filament"), lcd_menu_AutoLoadFilament);////MSG_AUTOLOAD_FILAMENT c=18
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -5553,7 +5556,7 @@ static void lcd_main_menu()
|
||||||
#if defined(TMC2130) || defined(FILAMENT_SENSOR)
|
#if defined(TMC2130) || defined(FILAMENT_SENSOR)
|
||||||
MENU_ITEM_SUBMENU_P(_i("Fail stats"), lcd_menu_fails_stats);////MSG_FAIL_STATS c=18
|
MENU_ITEM_SUBMENU_P(_i("Fail stats"), lcd_menu_fails_stats);////MSG_FAIL_STATS c=18
|
||||||
#endif
|
#endif
|
||||||
if (mmu_enabled) {
|
if (MMU2::mmu2.Enabled()) {
|
||||||
MENU_ITEM_SUBMENU_P(_i("Fail stats MMU"), lcd_menu_fails_stats_mmu);////MSG_MMU_FAIL_STATS c=18
|
MENU_ITEM_SUBMENU_P(_i("Fail stats MMU"), lcd_menu_fails_stats_mmu);////MSG_MMU_FAIL_STATS c=18
|
||||||
}
|
}
|
||||||
MENU_ITEM_SUBMENU_P(_i("Support"), lcd_support_menu);////MSG_SUPPORT c=18
|
MENU_ITEM_SUBMENU_P(_i("Support"), lcd_support_menu);////MSG_SUPPORT c=18
|
||||||
|
|
@ -5902,7 +5905,7 @@ void print_stop()
|
||||||
fanSpeed = 0;
|
fanSpeed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mmu_enabled) extr_unload(); //M702 C
|
if (MMU2::mmu2.Enabled()) MMU2::mmu2.unload(); //M702 C
|
||||||
finishAndDisableSteppers(); //M84
|
finishAndDisableSteppers(); //M84
|
||||||
axis_relative_modes = E_AXIS_MASK; //XYZ absolute, E relative
|
axis_relative_modes = E_AXIS_MASK; //XYZ absolute, E relative
|
||||||
}
|
}
|
||||||
|
|
@ -6228,7 +6231,7 @@ bool lcd_selftest()
|
||||||
//! As the Fsensor Detection isn't yet ready for the mmu2s we set temporarily the IR sensor 0.3 or older for mmu2s
|
//! As the Fsensor Detection isn't yet ready for the mmu2s we set temporarily the IR sensor 0.3 or older for mmu2s
|
||||||
//! @todo Don't forget to remove this as soon Fsensor Detection works with mmu
|
//! @todo Don't forget to remove this as soon Fsensor Detection works with mmu
|
||||||
if(fsensor.getSensorRevision() == IR_sensor_analog::SensorRevision::_Undef) {
|
if(fsensor.getSensorRevision() == IR_sensor_analog::SensorRevision::_Undef) {
|
||||||
if (!mmu_enabled) {
|
if (!MMU2::mmu2.Enabled()) {
|
||||||
lcd_detect_IRsensor();
|
lcd_detect_IRsensor();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -6424,7 +6427,7 @@ bool lcd_selftest()
|
||||||
if (_result)
|
if (_result)
|
||||||
{
|
{
|
||||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||||
if (mmu_enabled)
|
if (MMU2::mmu2.Enabled())
|
||||||
{
|
{
|
||||||
_progress = lcd_selftest_screen(TestScreen::Fsensor, _progress, 3, true, 2000); //check filaments sensor
|
_progress = lcd_selftest_screen(TestScreen::Fsensor, _progress, 3, true, 2000); //check filaments sensor
|
||||||
_result = selftest_irsensor();
|
_result = selftest_irsensor();
|
||||||
|
|
@ -7048,19 +7051,18 @@ static bool selftest_irsensor()
|
||||||
{
|
{
|
||||||
TempBackup tempBackup;
|
TempBackup tempBackup;
|
||||||
setTargetHotend(ABS_PREHEAT_HOTEND_TEMP,active_extruder);
|
setTargetHotend(ABS_PREHEAT_HOTEND_TEMP,active_extruder);
|
||||||
mmu_wait_for_heater_blocking();
|
//@@TODO mmu_wait_for_heater_blocking();
|
||||||
progress = lcd_selftest_screen(TestScreen::Fsensor, 0, 1, true, 0);
|
progress = lcd_selftest_screen(TestScreen::Fsensor, 0, 1, true, 0);
|
||||||
mmu_filament_ramming();
|
//@@TODO mmu_filament_ramming();
|
||||||
}
|
}
|
||||||
progress = lcd_selftest_screen(TestScreen::Fsensor, progress, 1, true, 0);
|
progress = lcd_selftest_screen(TestScreen::Fsensor, progress, 1, true, 0);
|
||||||
mmu_command(MmuCmd::U0);
|
MMU2::mmu2.unload(); // mmu_command(MmuCmd::U0); manage_response(false, false);
|
||||||
manage_response(false, false);
|
|
||||||
|
|
||||||
for(uint_least8_t i = 0; i < 200; ++i)
|
for(uint_least8_t i = 0; i < 200; ++i)
|
||||||
{
|
{
|
||||||
if (0 == (i % 32)) progress = lcd_selftest_screen(TestScreen::Fsensor, progress, 1, true, 0);
|
if (0 == (i % 32)) progress = lcd_selftest_screen(TestScreen::Fsensor, progress, 1, true, 0);
|
||||||
|
|
||||||
mmu_load_step(false);
|
//@@TODO mmu_load_step(false);
|
||||||
while (blocks_queued())
|
while (blocks_queued())
|
||||||
{
|
{
|
||||||
if (fsensor.getFilamentPresent())
|
if (fsensor.getFilamentPresent())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue