| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577 | //! @file#include "mmu.h"#include "planner.h"#include "language.h"#include "lcd.h"#include "uart2.h"#include "temperature.h"#include "Configuration_prusa.h"#include "fsensor.h"#include "cardreader.h"#include "ultralcd.h"#include "sound.h"#include "printers.h"#include <avr/pgmspace.h>#include "io_atmega2560.h"#include "AutoDeplete.h"#ifdef TMC2130#include "tmc2130.h"#endif //TMC2130#define MMU_TODELAY 100#define MMU_TIMEOUT 10#define MMU_CMD_TIMEOUT 45000ul //45s timeout for mmu commands (except P0)#define MMU_P0_TIMEOUT 3000ul //timeout for P0 command: 3seconds#define MMU_MAX_RESEND_ATTEMPTS 2#ifdef MMU_HWRESET#define MMU_RST_PIN 76#endif //MMU_HWRESETnamespace{    enum class S : uint_least8_t    {        WaitStealthMode,        GetFindaInit,        GetBuildNr,        GetVersion,        Init,        Disabled,        Idle,        GetFinda,        WaitCmd, //!< wait for command response        Pause,        GetDrvError, //!< get power failures count		SwitchMode //switch mmu between stealth and normal mode     };}bool mmu_enabled = false;bool mmu_ready = false;bool mmu_fil_loaded = false; //if true: blocks execution of duplicit T-codesstatic S mmu_state = S::Disabled;MmuCmd mmu_cmd = MmuCmd::None;//idler ir sensorstatic uint8_t mmu_idl_sens = 0;bool ir_sensor_detected = false; static bool mmu_loading_flag = false; //when set to true, we assume that mmu2 unload was finished and loading phase is now performed; printer can send 'A' to mmu2 to abort loading processuint8_t mmu_extruder = MMU_FILAMENT_UNKNOWN;//! This variable probably has no meaning and is planed to be removeduint8_t tmp_extruder = MMU_FILAMENT_UNKNOWN;int8_t mmu_finda = -1;int16_t mmu_version = -1;int16_t mmu_buildnr = -1;uint32_t mmu_last_request = 0;uint32_t mmu_last_response = 0;MmuCmd mmu_last_cmd = MmuCmd::None;uint16_t mmu_power_failures = 0;#ifdef MMU_DEBUGstatic const auto DEBUG_PUTS_P = puts_P;static const auto DEBUG_PRINTF_P = printf_P;#else //MMU_DEBUG#define DEBUG_PUTS_P(str)#define DEBUG_PRINTF_P( __fmt, ... )#endif //MMU_DEBUG#if defined(MMU_FINDA_DEBUG) && defined(MMU_DEBUG)static const auto FDEBUG_PUTS_P = puts_P;static const auto FDEBUG_PRINTF_P = printf_P;#else#define FDEBUG_PUTS_P(str)#define FDEBUG_PRINTF_P( __fmt, ... )#endif //defined(MMU_FINDA_DEBUG) && defined(MMU_DEBUG)//clear rx buffervoid mmu_clr_rx_buf(void){	while (fgetc(uart2io) >= 0);}//send command - putsint mmu_puts_P(const char* str){	mmu_clr_rx_buf();                          //clear rx buffer    int r = fputs_P(str, uart2io);             //send command	mmu_last_request = _millis();	return r;}//send command - printfint mmu_printf_P(const char* format, ...){	va_list args;	va_start(args, format);	mmu_clr_rx_buf();                          //clear rx buffer	int r = vfprintf_P(uart2io, format, args); //send command	va_end(args);	mmu_last_request = _millis();	return r;}//check 'ok' responseint8_t mmu_rx_ok(void){	int8_t res = uart2_rx_str_P(PSTR("ok\n"));	if (res == 1) mmu_last_response = _millis();	return res;}//check 'start' responseint8_t mmu_rx_start(void){	int8_t res = uart2_rx_str_P(PSTR("start\n"));	if (res == 1) mmu_last_response = _millis();	return res;}//initialize mmu2 unit - first part - should be done at begining of startup processvoid mmu_init(void){#ifdef MMU_HWRESET	digitalWrite(MMU_RST_PIN, HIGH);	pinMode(MMU_RST_PIN, OUTPUT);              //setup reset pin#endif //MMU_HWRESET	uart2_init();                              //init uart2	_delay_ms(10);                             //wait 10ms for sure	mmu_reset();                               //reset mmu (HW or SW), do not wait for response	mmu_state = S::Init;	PIN_INP(IR_SENSOR_PIN); //input mode	PIN_SET(IR_SENSOR_PIN); //pullup}//if IR_SENSOR defined, always returns true//otherwise check for ir sensor and returns true if idler IR sensor was detected, otherwise returns falsebool check_for_ir_sensor() {#ifdef IR_SENSOR	return true;#else //IR_SENSOR	bool detected = false;	//if IR_SENSOR_PIN input is low and pat9125sensor is not present we detected idler sensor	if ((PIN_GET(IR_SENSOR_PIN) == 0) #ifdef PAT9125		&& fsensor_not_responding#endif //PAT9125	) 	{				detected = true;		//printf_P(PSTR("Idler IR sensor detected\n"));	}	else	{		//printf_P(PSTR("Idler IR sensor not detected\n"));	}	return detected;#endif //IR_SENSOR}static bool activate_stealth_mode(){#ifdef MMU_FORCE_STEALTH_MODE	return true;#else	return (eeprom_read_byte((uint8_t*)EEPROM_MMU_STEALTH) == 1);#endif}//mmu main loop - state machine processingvoid mmu_loop(void){	static uint8_t mmu_attempt_nr = 0;//	printf_P(PSTR("MMU loop, state=%d\n"), mmu_state);	switch (mmu_state)	{	case S::Disabled:		return;	case S::Init:		if (mmu_rx_start() > 0)		{		    DEBUG_PUTS_P(PSTR("MMU => 'start'"));		    DEBUG_PUTS_P(PSTR("MMU <= 'S1'"));		    mmu_puts_P(PSTR("S1\n")); //send 'read version' request			mmu_state = S::GetVersion;		}		else if (_millis() > 30000) //30sec after reset disable mmu		{			puts_P(PSTR("MMU not responding - DISABLED"));			mmu_state = S::Disabled;		}		return;	case S::GetVersion:		if (mmu_rx_ok() > 0)		{			fscanf_P(uart2io, PSTR("%u"), &mmu_version); //scan version from buffer			DEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_version);			DEBUG_PUTS_P(PSTR("MMU <= 'S2'"));			mmu_puts_P(PSTR("S2\n")); //send 'read buildnr' request			mmu_state = S::GetBuildNr;		}		return;	case S::GetBuildNr:		if (mmu_rx_ok() > 0)		{			fscanf_P(uart2io, PSTR("%u"), &mmu_buildnr); //scan buildnr from buffer			DEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_buildnr);			bool version_valid = mmu_check_version();			if (!version_valid) mmu_show_warning();			else puts_P(PSTR("MMU version valid"));						if (!activate_stealth_mode())			{				FDEBUG_PUTS_P(PSTR("MMU <= 'P0'"));				mmu_puts_P(PSTR("P0\n")); //send 'read finda' request				mmu_state = S::GetFindaInit;			}			else			{				DEBUG_PUTS_P(PSTR("MMU <= 'M1'"));				mmu_puts_P(PSTR("M1\n")); //set mmu mode to stealth				mmu_state = S::WaitStealthMode;			}		}		return;	case S::WaitStealthMode:		if (mmu_rx_ok() > 0)		{			FDEBUG_PUTS_P(PSTR("MMU <= 'P0'"));		    mmu_puts_P(PSTR("P0\n")); //send 'read finda' request			mmu_state = S::GetFindaInit;		}		return;	case S::GetFindaInit:		if (mmu_rx_ok() > 0)		{			fscanf_P(uart2io, PSTR("%hhu"), &mmu_finda); //scan finda from buffer			FDEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_finda);			puts_P(PSTR("MMU - ENABLED"));			mmu_enabled = true;			mmu_state = S::Idle;		}		return;	case S::Idle:		if (mmu_cmd != MmuCmd::None) //command request ?		{			if ((mmu_cmd >= MmuCmd::T0) && (mmu_cmd <= MmuCmd::T4))			{				const uint8_t filament = mmu_cmd - MmuCmd::T0;				DEBUG_PRINTF_P(PSTR("MMU <= 'T%d'\n"), filament);				mmu_printf_P(PSTR("T%d\n"), filament);				mmu_state = S::WaitCmd; // wait for response				mmu_fil_loaded = true;				mmu_idl_sens = 1;			}			else if ((mmu_cmd >= MmuCmd::L0) && (mmu_cmd <= MmuCmd::L4))			{			    const uint8_t filament = mmu_cmd - MmuCmd::L0;			    DEBUG_PRINTF_P(PSTR("MMU <= 'L%d'\n"), filament);			    mmu_printf_P(PSTR("L%d\n"), filament);			    mmu_state = S::WaitCmd; // wait for response			}			else if (mmu_cmd == MmuCmd::C0)			{			    DEBUG_PRINTF_P(PSTR("MMU <= 'C0'\n"));				mmu_puts_P(PSTR("C0\n")); //send 'continue loading'				mmu_state = S::WaitCmd;				mmu_idl_sens = 1;			}			else if (mmu_cmd == MmuCmd::U0)			{			    DEBUG_PRINTF_P(PSTR("MMU <= 'U0'\n"));				mmu_puts_P(PSTR("U0\n")); //send 'unload current filament'				mmu_fil_loaded = false;				mmu_state = S::WaitCmd;			}			else if ((mmu_cmd >= MmuCmd::E0) && (mmu_cmd <= MmuCmd::E4))			{			    const uint8_t filament = mmu_cmd - MmuCmd::E0;				DEBUG_PRINTF_P(PSTR("MMU <= 'E%d'\n"), filament);				mmu_printf_P(PSTR("E%d\n"), filament); //send eject filament				mmu_fil_loaded = false;				mmu_state = S::WaitCmd;			}			else if ((mmu_cmd >= MmuCmd::K0) && (mmu_cmd <= MmuCmd::K4))            {                const uint8_t filament = mmu_cmd - MmuCmd::K0;                DEBUG_PRINTF_P(PSTR("MMU <= 'K%d'\n"), filament);                mmu_printf_P(PSTR("K%d\n"), filament); //send eject filament                mmu_fil_loaded = false;                mmu_state = S::WaitCmd;            }			else if (mmu_cmd == MmuCmd::R0)			{			    DEBUG_PRINTF_P(PSTR("MMU <= 'R0'\n"));				mmu_puts_P(PSTR("R0\n")); //send recover after eject				mmu_state = S::WaitCmd;			}			else if (mmu_cmd == MmuCmd::S3)			{			    DEBUG_PRINTF_P(PSTR("MMU <= 'S3'\n"));				mmu_puts_P(PSTR("S3\n")); //send power failures request				mmu_state = S::GetDrvError;			}			else if (mmu_cmd == MmuCmd::W0)			{			    DEBUG_PRINTF_P(PSTR("MMU <= 'W0'\n"));			    mmu_puts_P(PSTR("W0\n"));			    mmu_state = S::Pause;			}			mmu_last_cmd = mmu_cmd;			mmu_cmd = MmuCmd::None;		}		else if ((eeprom_read_byte((uint8_t*)EEPROM_MMU_STEALTH) != SilentModeMenu_MMU) && mmu_ready) {				DEBUG_PRINTF_P(PSTR("MMU <= 'M%d'\n"), SilentModeMenu_MMU);				mmu_printf_P(PSTR("M%d\n"), SilentModeMenu_MMU);				mmu_state = S::SwitchMode;		}		else if ((mmu_last_response + 300) < _millis()) //request every 300ms		{#ifndef IR_SENSOR			if(check_for_ir_sensor()) ir_sensor_detected = true;#endif //IR_SENSOR not defined			FDEBUG_PUTS_P(PSTR("MMU <= 'P0'"));		    mmu_puts_P(PSTR("P0\n")); //send 'read finda' request			mmu_state = S::GetFinda;		}		return;	case S::GetFinda: //response to command P0        if (mmu_idl_sens)        {            if (PIN_GET(IR_SENSOR_PIN) == 0 && mmu_loading_flag)            {#ifdef MMU_DEBUG                printf_P(PSTR("MMU <= 'A'\n"));#endif //MMU_DEBUG                  mmu_puts_P(PSTR("A\n")); //send 'abort' request                mmu_idl_sens = 0;                //printf_P(PSTR("MMU IDLER_SENSOR = 0 - ABORT\n"));            }            //else                //printf_P(PSTR("MMU IDLER_SENSOR = 1 - WAIT\n"));        }		if (mmu_rx_ok() > 0)		{			fscanf_P(uart2io, PSTR("%hhu"), &mmu_finda); //scan finda from buffer			FDEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_finda);			//printf_P(PSTR("Eact: %d\n"), int(e_active()));			if (!mmu_finda && CHECK_FSENSOR && fsensor_enabled) {				fsensor_stop_and_save_print();				enquecommand_front_P(PSTR("PRUSA fsensor_recover")); //then recover				ad_markDepleted(mmu_extruder);				if (lcd_autoDepleteEnabled() && !ad_allDepleted())				{				    enquecommand_front_P(PSTR("M600 AUTO")); //save print and run M600 command				}				else				{				    enquecommand_front_P(PSTR("M600")); //save print and run M600 command				}			}			mmu_state = S::Idle;			if (mmu_cmd == MmuCmd::None)				mmu_ready = true;		}		else if ((mmu_last_request + MMU_P0_TIMEOUT) < _millis())		{ //resend request after timeout (30s)			mmu_state = S::Idle;		}		return;	case S::WaitCmd: //response to mmu commands        if (mmu_idl_sens)        {            if (PIN_GET(IR_SENSOR_PIN) == 0 && mmu_loading_flag)            {                DEBUG_PRINTF_P(PSTR("MMU <= 'A'\n"));                mmu_puts_P(PSTR("A\n")); //send 'abort' request                mmu_idl_sens = 0;                //printf_P(PSTR("MMU IDLER_SENSOR = 0 - ABORT\n"));            }            //else                //printf_P(PSTR("MMU IDLER_SENSOR = 1 - WAIT\n"));        }		if (mmu_rx_ok() > 0)		{		    DEBUG_PRINTF_P(PSTR("MMU => 'ok'\n"));			mmu_attempt_nr = 0;			mmu_last_cmd = MmuCmd::None;			mmu_ready = true;			mmu_state = S::Idle;		}		else if ((mmu_last_request + MMU_CMD_TIMEOUT) < _millis())		{ //resend request after timeout (5 min)			if (mmu_last_cmd >= MmuCmd::T0 && mmu_last_cmd <= MmuCmd::T4)			{				if (mmu_attempt_nr++ < MMU_MAX_RESEND_ATTEMPTS) {				    DEBUG_PRINTF_P(PSTR("MMU retry attempt nr. %d\n"), mmu_attempt_nr - 1);					mmu_cmd = mmu_last_cmd;				}				else {					mmu_cmd = MmuCmd::None;					mmu_last_cmd = MmuCmd::None; //check					mmu_attempt_nr = 0;				}			}			mmu_state = S::Idle;		}		return;	case S::Pause:        if (mmu_rx_ok() > 0)        {            DEBUG_PRINTF_P(PSTR("MMU => 'ok', resume print\n"));            mmu_attempt_nr = 0;            mmu_last_cmd = MmuCmd::None;            mmu_ready = true;            mmu_state = S::Idle;            lcd_resume_print();        }        if (mmu_cmd != MmuCmd::None)        {            mmu_state = S::Idle;        }	    return;	case S::GetDrvError:		if (mmu_rx_ok() > 0)		{			fscanf_P(uart2io, PSTR("%d"), &mmu_power_failures); //scan power failures			DEBUG_PRINTF_P(PSTR("MMU => 'ok'\n"));			mmu_last_cmd = MmuCmd::None;			mmu_ready = true;			mmu_state = S::Idle;		}		else if ((mmu_last_request + MMU_CMD_TIMEOUT) < _millis())		{ //timeout 45 s			mmu_state = S::Idle;		}		return;	case S::SwitchMode:		if (mmu_rx_ok() > 0)		{			DEBUG_PRINTF_P(PSTR("MMU => 'ok'\n"));			eeprom_update_byte((uint8_t*)EEPROM_MMU_STEALTH, SilentModeMenu_MMU);			mmu_state = S::Idle;		}		else if ((mmu_last_request + MMU_CMD_TIMEOUT) < _millis())		{ //timeout 45 s			mmu_state = S::Idle;		}		return;			}}void mmu_reset(void){#ifdef MMU_HWRESET                             //HW - pulse reset pin	digitalWrite(MMU_RST_PIN, LOW);	_delay_us(100);	digitalWrite(MMU_RST_PIN, HIGH);#else                                          //SW - send X0 command    mmu_puts_P(PSTR("X0\n"));#endif}int8_t mmu_set_filament_type(uint8_t extruder, uint8_t filament){	printf_P(PSTR("MMU <= 'F%d %d'\n"), extruder, filament);	mmu_printf_P(PSTR("F%d %d\n"), extruder, filament);	unsigned char timeout = MMU_TIMEOUT;       //10x100ms	while ((mmu_rx_ok() <= 0) && (--timeout))		delay_keep_alive(MMU_TODELAY);	return timeout?1:0;}//! @brief Enqueue MMUv2 command//!//! Call manage_response() after enqueuing to process command.//! If T command is enqueued, it disables current for extruder motor if TMC2130 driver present.//! If T or L command is enqueued, it marks filament loaded in AutoDeplete module.void mmu_command(MmuCmd cmd){	if ((cmd >= MmuCmd::T0) && (cmd <= MmuCmd::T4))	{		//disable extruder motor#ifdef TMC2130		tmc2130_set_pwr(E_AXIS, 0);#endif //TMC2130		//printf_P(PSTR("E-axis disabled\n"));		ad_markLoaded(cmd - MmuCmd::T0);	}    if ((cmd >= MmuCmd::L0) && (cmd <= MmuCmd::L4))    {        ad_markLoaded(cmd - MmuCmd::L0);    }	mmu_cmd = cmd;	mmu_ready = false;}//! @brief Rotate extruder idler to catch filament//! @par synchronize//!  * true blocking call//!  * false non-blocking callvoid mmu_load_step(bool synchronize){		current_position[E_AXIS] = current_position[E_AXIS] + MMU_LOAD_FEEDRATE * 0.1;		plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], MMU_LOAD_FEEDRATE, active_extruder);		if (synchronize) st_synchronize();}//! @brief Is nozzle hot enough to move extruder wheels and do we have idler sensor?//!//! Do load steps only if temperature is higher then min. temp for safe extrusion and//! idler sensor present.//! Otherwise "cold extrusion prevented" would be send to serial line periodically//! and watchdog reset will be triggered by lack of keep_alive processing.//!//! @retval true temperature is high enough to move extruder//! @retval false temperature is not high enough to move extruder, turned//!         off E-stepper to prevent over-heating and allow filament pull-out if necessarybool can_extrude(){    if ((degHotend(active_extruder) < EXTRUDE_MINTEMP) || !ir_sensor_detected)    {        disable_e0();        delay_keep_alive(100);        return false;    }    return true;}static void get_response_print_info(uint8_t move) {	printf_P(PSTR("mmu_get_response - begin move: "), move);	switch (move) {		case MMU_LOAD_MOVE: printf_P(PSTR("load\n")); break;		case MMU_UNLOAD_MOVE: printf_P(PSTR("unload\n")); break;		case MMU_TCODE_MOVE: printf_P(PSTR("T-code\n")); break;		case MMU_NO_MOVE: printf_P(PSTR("no move\n")); break;		default: printf_P(PSTR("error: unknown move\n")); break;	}}bool mmu_get_response(uint8_t move){	get_response_print_info(move);	KEEPALIVE_STATE(IN_PROCESS);	while (mmu_cmd != MmuCmd::None)	{		delay_keep_alive(100);	}	while (!mmu_ready)	{		if ((mmu_state != S::WaitCmd) && (mmu_last_cmd == MmuCmd::None))			break;		switch (move) {			case MMU_LOAD_MOVE:			    mmu_loading_flag = true;				if (can_extrude()) mmu_load_step();				//don't rely on "ok" signal from mmu unit; if filament detected by idler sensor during loading stop loading movements to prevent infinite loading				if (PIN_GET(IR_SENSOR_PIN) == 0) move = MMU_NO_MOVE;				break;			case MMU_UNLOAD_MOVE:				if (PIN_GET(IR_SENSOR_PIN) == 0) //filament is still detected by idler sensor, printer helps with unlading 				{				    if (can_extrude())				    {                        printf_P(PSTR("Unload 1\n"));                        current_position[E_AXIS] = current_position[E_AXIS] - MMU_LOAD_FEEDRATE * MMU_LOAD_TIME_MS*0.001;                        plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], MMU_LOAD_FEEDRATE, active_extruder);                        st_synchronize();				    }				}				else //filament was unloaded from idler, no additional movements needed 				{ 					printf_P(PSTR("Unloading finished 1\n"));					disable_e0(); //turn off E-stepper to prevent overheating and alow filament pull-out if necessary					move = MMU_NO_MOVE;				}				break;			case MMU_TCODE_MOVE: //first do unload and then continue with infinite loading movements				if (PIN_GET(IR_SENSOR_PIN) == 0) //filament detected by idler sensor, we must unload first 				{                    if (can_extrude())                    {                        printf_P(PSTR("Unload 2\n"));                        current_position[E_AXIS] = current_position[E_AXIS] - MMU_LOAD_FEEDRATE * MMU_LOAD_TIME_MS*0.001;                        plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], MMU_LOAD_FEEDRATE, active_extruder);                        st_synchronize();                    }				}				else //delay to allow mmu unit to pull out filament from bondtech gears and then start with infinite loading 				{ 					printf_P(PSTR("Unloading finished 2\n"));					disable_e0(); //turn off E-stepper to prevent overheating and alow filament pull-out if necessary					delay_keep_alive(MMU_LOAD_TIME_MS);					move = MMU_LOAD_MOVE;					get_response_print_info(move);				}				break;			case MMU_NO_MOVE:			default: 				delay_keep_alive(100);				break;		}	}	printf_P(PSTR("mmu_get_response() returning: %d\n"), mmu_ready);	bool ret = mmu_ready;	mmu_ready = false;//	printf_P(PSTR("mmu_get_response - end %d\n"), ret?1:0);	return ret;}//! @brief Wait for active extruder to reach temperature set//!//! This function is blocking and showing lcd_wait_for_heater() screen//! which is constantly updated with nozzle temperature.void mmu_wait_for_heater_blocking(){    while ((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)    {        delay_keep_alive(1000);        lcd_wait_for_heater();    }}void manage_response(bool move_axes, bool turn_off_nozzle, uint8_t move){	bool response = false;	mmu_print_saved = false;	bool lcd_update_was_enabled = false;	float hotend_temp_bckp = degTargetHotend(active_extruder);	float z_position_bckp = current_position[Z_AXIS];	float x_position_bckp = current_position[X_AXIS];	float y_position_bckp = current_position[Y_AXIS];		uint8_t screen = 0; //used for showing multiscreen messages	mmu_loading_flag = false;	while(!response)	{		  response = mmu_get_response(move); //wait for "ok" from mmu		  if (!response) { //no "ok" was received in reserved time frame, user will fix the issue on mmu unit			  if (!mmu_print_saved) { //first occurence, we are saving current position, park print head in certain position and disable nozzle heater				  				  uint8_t mmu_fail = eeprom_read_byte((uint8_t*)EEPROM_MMU_FAIL);				  uint16_t mmu_fail_tot = eeprom_read_word((uint16_t*)EEPROM_MMU_FAIL_TOT);				  if(mmu_fail < 255) eeprom_update_byte((uint8_t*)EEPROM_MMU_FAIL, mmu_fail + 1);				  if(mmu_fail_tot < 65535) eeprom_update_word((uint16_t*)EEPROM_MMU_FAIL_TOT, mmu_fail_tot + 1);				  if (lcd_update_enabled) {					  lcd_update_was_enabled = true;					  lcd_update_enable(false);				  }				  st_synchronize();				  mmu_print_saved = true;				  printf_P(PSTR("MMU not responding\n"));				  KEEPALIVE_STATE(PAUSED_FOR_USER);				  hotend_temp_bckp = degTargetHotend(active_extruder);				  if (move_axes) {					  z_position_bckp = current_position[Z_AXIS];					  x_position_bckp = current_position[X_AXIS];					  y_position_bckp = current_position[Y_AXIS];				  					  //lift z					  current_position[Z_AXIS] += Z_PAUSE_LIFT;					  if (current_position[Z_AXIS] > Z_MAX_POS) current_position[Z_AXIS] = Z_MAX_POS;					  plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 15, active_extruder);					  st_synchronize();					  					  					  //Move XY to side					  current_position[X_AXIS] = X_PAUSE_POS;					  current_position[Y_AXIS] = Y_PAUSE_POS;					  plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 50, active_extruder);					  st_synchronize();				  }				  if (turn_off_nozzle) {					  //set nozzle target temperature to 0					  setAllTargetHotends(0);				  }				  disable_e0(); //turn off E-stepper to prevent overheating and alow filament pull-out if necessary			  }			  //first three lines are used for printing multiscreen message; last line contains measured and target nozzle temperature			  if (screen == 0) { //screen 0				  lcd_display_message_fullscreen_P(_i("MMU needs user attention."));				  screen++;			  }			  else {  //screen 1				  if((degTargetHotend(active_extruder) == 0) && turn_off_nozzle) lcd_display_message_fullscreen_P(_i("Press the knob to resume nozzle temperature."));				  else lcd_display_message_fullscreen_P(_i("Fix the issue and then press button on MMU unit."));				  screen=0;			  }			  lcd_set_degree();			  //5 seconds delay			  for (uint8_t i = 0; i < 5; i++) {				  if (lcd_clicked()) {					  setTargetHotend(hotend_temp_bckp, active_extruder);					 /// mmu_cmd = mmu_last_cmd;					  break;				  }		  				  //Print the hotend temperature (9 chars total) and fill rest of the line with space				  lcd_set_cursor(0, 4); //line 4				  int chars = lcd_printf_P(_N("%c%3d/%d%c"), LCD_STR_THERMOMETER[0],(int)(degHotend(active_extruder) + 0.5), (int)(degTargetHotend(active_extruder) + 0.5), LCD_STR_DEGREE[0]);				  lcd_space(9 - chars);				  delay_keep_alive(1000);			  }		  }		  else if (mmu_print_saved) {			  printf_P(PSTR("MMU starts responding\n"));			  KEEPALIVE_STATE(IN_HANDLER);			  mmu_loading_flag = false;			  if (turn_off_nozzle) 			  {				lcd_clear();				setTargetHotend(hotend_temp_bckp, active_extruder);				if (((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)) {					lcd_display_message_fullscreen_P(_i("MMU OK. Resuming temperature..."));					delay_keep_alive(3000);				}                mmu_wait_for_heater_blocking();			  }			  			  if (move_axes) {				  lcd_clear();				  lcd_display_message_fullscreen_P(_i("MMU OK. Resuming position..."));				  current_position[X_AXIS] = x_position_bckp;				  current_position[Y_AXIS] = y_position_bckp;				  plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 50, active_extruder);				  st_synchronize();				  current_position[Z_AXIS] = z_position_bckp;				  plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 15, active_extruder);				  st_synchronize();			  }			  else {				  lcd_clear();				  lcd_display_message_fullscreen_P(_i("MMU OK. Resuming..."));				  delay_keep_alive(1000); //delay just for showing MMU OK message for a while in case that there are no xyz movements			  }		  }	}	if (lcd_update_was_enabled) lcd_update_enable(true);#ifdef TMC2130			//enable extruder motor (disabled in mmu_command, start of T-code processing)			tmc2130_set_pwr(E_AXIS, 1);			//printf_P(PSTR("E-axis enabled\n"));#endif //TMC2130}//! @brief load filament to nozzle of multimaterial printer//!//! This function is used only only after T? (user select filament) and M600 (change filament).//! It is not used after T0 .. T4 command (select filament), in such case, gcode is responsible for loading//! filament to nozzle.//!void mmu_load_to_nozzle(){	st_synchronize();		bool saved_e_relative_mode = axis_relative_modes[E_AXIS];	if (!saved_e_relative_mode) axis_relative_modes[E_AXIS] = true;	if (ir_sensor_detected)	{		current_position[E_AXIS] += 3.0f;	}	else	{		current_position[E_AXIS] += 7.2f;	}    float feedrate = 562;	plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feedrate / 60, active_extruder);    st_synchronize();	current_position[E_AXIS] += 14.4f;	feedrate = 871;	plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feedrate / 60, active_extruder);    st_synchronize();	current_position[E_AXIS] += 36.0f;	feedrate = 1393;	plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feedrate / 60, active_extruder);    st_synchronize();	current_position[E_AXIS] += 14.4f;	feedrate = 871;	plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feedrate / 60, active_extruder);    st_synchronize();	if (!saved_e_relative_mode) axis_relative_modes[E_AXIS] = false;}void mmu_M600_wait_and_beep() {		//Beep and wait for user to remove old filament and prepare new filament for load		KEEPALIVE_STATE(PAUSED_FOR_USER);		int counterBeep = 0;		lcd_display_message_fullscreen_P(_i("Remove old filament and press the knob to start loading new filament."));		bool bFirst=true;		while (!lcd_clicked()){			manage_heater();			manage_inactivity(true);			#if BEEPER > 0			if (counterBeep == 500) {				counterBeep = 0;			}			SET_OUTPUT(BEEPER);			if (counterBeep == 0) {				if((eSoundMode==e_SOUND_MODE_LOUD)||((eSoundMode==e_SOUND_MODE_ONCE)&&bFirst))				{					bFirst=false;					WRITE(BEEPER, HIGH);				}			}			if (counterBeep == 20) {				WRITE(BEEPER, LOW);			}							counterBeep++;			#endif //BEEPER > 0			delay_keep_alive(4);		}		WRITE(BEEPER, LOW);}//! @brief load filament for mmu v2//! @par nozzle_temp nozzle temperature to load filamentvoid mmu_M600_load_filament(bool automatic, float nozzle_temp){     tmp_extruder = mmu_extruder;    if (!automatic)    {    #ifdef MMU_M600_SWITCH_EXTRUDER        bool yes = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Do you want to switch extruder?"), false);        if(yes) tmp_extruder = choose_extruder_menu();    #endif //MMU_M600_SWITCH_EXTRUDER    }    else    {        tmp_extruder = ad_getAlternative(tmp_extruder);    }    lcd_update_enable(false);    lcd_clear();    lcd_set_cursor(0, 1); lcd_puts_P(_T(MSG_LOADING_FILAMENT));    lcd_print(" ");    lcd_print(tmp_extruder + 1);    snmm_filaments_used |= (1 << tmp_extruder); //for stop print    //printf_P(PSTR("T code: %d \n"), tmp_extruder);    //mmu_printf_P(PSTR("T%d\n"), tmp_extruder);    setTargetHotend(nozzle_temp,active_extruder);    mmu_wait_for_heater_blocking();    mmu_command(MmuCmd::T0 + tmp_extruder);    manage_response(false, true, MMU_LOAD_MOVE);    mmu_continue_loading(is_usb_printing);    mmu_extruder = tmp_extruder; //filament change is finished    mmu_load_to_nozzle();    load_filament_final_feed();    st_synchronize();}#ifdef SNMMvoid extr_mov(float shift, float feed_rate){ //move extruder no matter what the current heater temperature is	set_extrude_min_temp(.0);	current_position[E_AXIS] += shift;	plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feed_rate, active_extruder);	set_extrude_min_temp(EXTRUDE_MINTEMP);}#endif //SNMMvoid change_extr(int#ifdef SNMM        extr#endif //SNMM        ) { //switches multiplexer for extruders#ifdef SNMM	st_synchronize();	_delay(100);	disable_e0();	disable_e1();	disable_e2();	mmu_extruder = extr;	pinMode(E_MUX0_PIN, OUTPUT);	pinMode(E_MUX1_PIN, OUTPUT);	switch (extr) {	case 1:		WRITE(E_MUX0_PIN, HIGH);		WRITE(E_MUX1_PIN, LOW);				break;	case 2:		WRITE(E_MUX0_PIN, LOW);		WRITE(E_MUX1_PIN, HIGH);				break;	case 3:		WRITE(E_MUX0_PIN, HIGH);		WRITE(E_MUX1_PIN, HIGH);				break;	default:		WRITE(E_MUX0_PIN, LOW);		WRITE(E_MUX1_PIN, LOW);				break;	}	_delay(100);#endif}int get_ext_nr(){ //reads multiplexer input pins and return current extruder number (counted from 0)#ifndef SNMM	return(mmu_extruder); //update needed#else 	return(2 * READ(E_MUX1_PIN) + READ(E_MUX0_PIN));#endif}void display_loading(){	switch (mmu_extruder) 	{	case 1: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T1)); break;	case 2: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T2)); break;	case 3: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T3)); break;	default: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T0)); break;	}}void extr_adj(uint8_t extruder) //loading filament for SNMM{#ifndef SNMM    MmuCmd cmd = MmuCmd::L0 + extruder;    if (cmd > MmuCmd::L4)    {        printf_P(PSTR("Filament out of range %d \n"),extruder);        return;    }    mmu_command(cmd);		//show which filament is currently loaded		lcd_update_enable(false);	lcd_clear();	lcd_set_cursor(0, 1); lcd_puts_P(_T(MSG_LOADING_FILAMENT));	//if(strlen(_T(MSG_LOADING_FILAMENT))>18) lcd.setCursor(0, 1);	//else lcd.print(" ");	lcd_print(" ");	lcd_print(extruder + 1);	// get response	manage_response(false, false);	lcd_update_enable(true);			//lcd_return_to_status();#else	bool correct;	max_feedrate[E_AXIS] =80;	//max_feedrate[E_AXIS] = 50;	START:	lcd_clear();	lcd_set_cursor(0, 0); 	switch (extruder) {	case 1: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T1)); break;	case 2: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T2)); break;	case 3: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T3)); break;	default: lcd_display_message_fullscreen_P(_T(MSG_FILAMENT_LOADING_T0)); break;   	}	KEEPALIVE_STATE(PAUSED_FOR_USER);	do{		extr_mov(0.001,1000);		delay_keep_alive(2);	} while (!lcd_clicked());	//delay_keep_alive(500);	KEEPALIVE_STATE(IN_HANDLER);	st_synchronize();	//correct = lcd_show_fullscreen_message_yes_no_and_wait_P(MSG_FIL_LOADED_CHECK, false);	//if (!correct) goto	START;	//extr_mov(BOWDEN_LENGTH/2.f, 500); //dividing by 2 is there because of max. extrusion length limitation (x_max + y_max)	//extr_mov(BOWDEN_LENGTH/2.f, 500);	extr_mov(bowden_length[extruder], 500);	lcd_clear();	lcd_set_cursor(0, 0); lcd_puts_P(_T(MSG_LOADING_FILAMENT));	if(strlen(_T(MSG_LOADING_FILAMENT))>18) lcd_set_cursor(0, 1);	else lcd_print(" ");	lcd_print(mmu_extruder + 1);	lcd_set_cursor(0, 2); lcd_puts_P(_T(MSG_PLEASE_WAIT));	st_synchronize();	max_feedrate[E_AXIS] = 50;	lcd_update_enable(true);	lcd_return_to_status();	lcdDrawUpdate = 2;#endif}struct E_step{    float extrude;   //!< extrude distance in mm    float feed_rate; //!< feed rate in mm/s};static const E_step ramming_sequence[] PROGMEM ={    {1.0,   1000.0/60},    {1.0,   1500.0/60},    {2.0,   2000.0/60},    {1.5,   3000.0/60},    {2.5,   4000.0/60},    {-15.0, 5000.0/60},    {-14.0, 1200.0/60},    {-6.0,  600.0/60},    {10.0,  700.0/60},    {-10.0, 400.0/60},    {-50.0, 2000.0/60},};//! @brief Unload sequence to optimize shape of the tip of the unloaded filamentvoid mmu_filament_ramming(){    for(uint8_t i = 0; i < (sizeof(ramming_sequence)/sizeof(E_step));++i)    {        current_position[E_AXIS] += pgm_read_float(&(ramming_sequence[i].extrude));        plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS],                current_position[E_AXIS], pgm_read_float(&(ramming_sequence[i].feed_rate)), active_extruder);        st_synchronize();    }}//-//void extr_unload_(){//if(bFilamentAction)if(0)	{     bFilamentAction=false;     extr_unload();     }else	{     eFilamentAction=eFILAMENT_ACTION::mmuUnLoad;     bFilamentFirstRun=false;     if(target_temperature[0]>=EXTRUDE_MINTEMP)          {          bFilamentPreheatState=true;          mFilamentItem(target_temperature[0],target_temperature_bed);          }//     else menu_submenu(mFilamentMenu);     else mFilamentMenu();	}}//! @brief show which filament is currently unloadedvoid extr_unload_view(){    lcd_clear();    lcd_set_cursor(0, 1); lcd_puts_P(_T(MSG_UNLOADING_FILAMENT));    lcd_print(" ");    if (mmu_extruder == MMU_FILAMENT_UNKNOWN) lcd_print(" ");    else lcd_print(mmu_extruder + 1);}void extr_unload(){ //unload just current filament for multimaterial printers#ifdef SNMM	float tmp_motor[3] = DEFAULT_PWM_MOTOR_CURRENT;	float tmp_motor_loud[3] = DEFAULT_PWM_MOTOR_CURRENT_LOUD;	uint8_t SilentMode = eeprom_read_byte((uint8_t*)EEPROM_SILENT);#endif	if (degHotend0() > EXTRUDE_MINTEMP)	{#ifndef SNMM		st_synchronize();        menu_submenu(extr_unload_view);		mmu_filament_ramming();		mmu_command(MmuCmd::U0);		// get response		manage_response(false, true, MMU_UNLOAD_MOVE);        menu_back();#else //SNMM		lcd_clear();		lcd_display_message_fullscreen_P(PSTR(""));		max_feedrate[E_AXIS] = 50;		lcd_set_cursor(0, 0); lcd_puts_P(_T(MSG_UNLOADING_FILAMENT));		lcd_print(" ");		lcd_print(mmu_extruder + 1);		lcd_set_cursor(0, 2); lcd_puts_P(_T(MSG_PLEASE_WAIT));		if (current_position[Z_AXIS] < 15) {			current_position[Z_AXIS] += 15; //lifting in Z direction to make space for extrusion			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 25, active_extruder);		}				current_position[E_AXIS] += 10; //extrusion		plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 10, active_extruder);		st_current_set(2, E_MOTOR_HIGH_CURRENT);		if (current_temperature[0] < 230) { //PLA & all other filaments			current_position[E_AXIS] += 5.4;			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 2800 / 60, active_extruder);			current_position[E_AXIS] += 3.2;			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 3000 / 60, active_extruder);			current_position[E_AXIS] += 3;			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 3400 / 60, active_extruder);		}		else { //ABS			current_position[E_AXIS] += 3.1;			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 2000 / 60, active_extruder);			current_position[E_AXIS] += 3.1;			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 2500 / 60, active_extruder);			current_position[E_AXIS] += 4;			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 3000 / 60, active_extruder);			/*current_position[X_AXIS] += 23; //delay			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 600 / 60, active_extruder); //delay			current_position[X_AXIS] -= 23; //delay			plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 600 / 60, active_extruder); //delay*/			delay_keep_alive(4700);		}			max_feedrate[E_AXIS] = 80;		current_position[E_AXIS] -= (bowden_length[mmu_extruder] + 60 + FIL_LOAD_LENGTH) / 2;		plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 500, active_extruder);		current_position[E_AXIS] -= (bowden_length[mmu_extruder] + 60 + FIL_LOAD_LENGTH) / 2;		plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 500, active_extruder);		st_synchronize();		//st_current_init();		if (SilentMode != SILENT_MODE_OFF) st_current_set(2, tmp_motor[2]); //set back to normal operation currents		else st_current_set(2, tmp_motor_loud[2]);		lcd_update_enable(true);		lcd_return_to_status();		max_feedrate[E_AXIS] = 50;#endif //SNMM	}	else	{		show_preheat_nozzle_warning();	}}//wrapper functions for loading filamentvoid extr_adj_0(){#ifndef SNMM	enquecommand_P(PSTR("M701 E0"));#else	change_extr(0);	extr_adj(0);#endif}void extr_adj_1(){#ifndef SNMM	enquecommand_P(PSTR("M701 E1"));#else	change_extr(1);	extr_adj(1);#endif}void extr_adj_2(){#ifndef SNMM	enquecommand_P(PSTR("M701 E2"));#else	change_extr(2);	extr_adj(2);#endif}void extr_adj_3(){#ifndef SNMM	enquecommand_P(PSTR("M701 E3"));#else	change_extr(3);	extr_adj(3);#endif}void extr_adj_4(){#ifndef SNMM	enquecommand_P(PSTR("M701 E4"));#else	change_extr(4);	extr_adj(4);#endif}void load_all(){#ifndef SNMM	enquecommand_P(PSTR("M701 E0"));	enquecommand_P(PSTR("M701 E1"));	enquecommand_P(PSTR("M701 E2"));	enquecommand_P(PSTR("M701 E3"));	enquecommand_P(PSTR("M701 E4"));#else	for (int i = 0; i < 4; i++)	{		change_extr(i);		extr_adj(i);	}#endif}//wrapper functions for changing extrudersvoid extr_change_0(){	change_extr(0);	lcd_return_to_status();}void extr_change_1(){	change_extr(1);	lcd_return_to_status();}void extr_change_2(){	change_extr(2);	lcd_return_to_status();}void extr_change_3(){	change_extr(3);	lcd_return_to_status();}#ifdef SNMM//wrapper functions for unloading filamentvoid extr_unload_all(){	if (degHotend0() > EXTRUDE_MINTEMP)	{		for (int i = 0; i < 4; i++)		{			change_extr(i);			extr_unload();		}	}	else	{		show_preheat_nozzle_warning();		lcd_return_to_status();	}}//unloading just used filament (for snmm)void extr_unload_used(){	if (degHotend0() > EXTRUDE_MINTEMP) {		for (int i = 0; i < 4; i++) {			if (snmm_filaments_used & (1 << i)) {				change_extr(i);				extr_unload();			}		}		snmm_filaments_used = 0;	}	else {		show_preheat_nozzle_warning();		lcd_return_to_status();	}}#endif //SNMMvoid extr_unload_0(){	change_extr(0);	extr_unload();}void extr_unload_1(){	change_extr(1);	extr_unload();}void extr_unload_2(){	change_extr(2);	extr_unload();}void extr_unload_3(){	change_extr(3);	extr_unload();}void extr_unload_4(){	change_extr(4);	extr_unload();}bool mmu_check_version(){	return (mmu_buildnr >= MMU_REQUIRED_FW_BUILDNR);}void mmu_show_warning(){	printf_P(PSTR("MMU2 firmware version invalid. Required version: build number %d or higher."), MMU_REQUIRED_FW_BUILDNR);	kill(_i("Please update firmware in your MMU2. Waiting for reset."));}void lcd_mmu_load_to_nozzle(uint8_t filament_nr){    menu_back();    bFilamentAction = false;                            // NOT in "mmu_load_to_nozzle_menu()"    if (degHotend0() > EXTRUDE_MINTEMP)    {        tmp_extruder = filament_nr;        lcd_update_enable(false);        lcd_clear();        lcd_set_cursor(0, 1);        lcd_puts_P(_T(MSG_LOADING_FILAMENT));        lcd_print(" ");        lcd_print(tmp_extruder + 1);        mmu_command(MmuCmd::T0 + tmp_extruder);        manage_response(true, true, MMU_TCODE_MOVE);        mmu_continue_loading(false);        mmu_extruder = tmp_extruder; //filament change is finished        marlin_rise_z();        mmu_load_to_nozzle();        load_filament_final_feed();        st_synchronize();        custom_message_type = CustomMsgTypes::F_LOAD;        lcd_setstatuspgm(_T(MSG_LOADING_FILAMENT));        lcd_return_to_status();        lcd_update_enable(true);        lcd_load_filament_color_check();        lcd_setstatuspgm(_T(WELCOME_MSG));        custom_message_type = CustomMsgTypes::STATUS;    }    else    {        show_preheat_nozzle_warning();    }}#ifdef MMU_HAS_CUTTERvoid mmu_cut_filament(uint8_t filament_nr){    menu_back();    bFilamentAction=false;                            // NOT in "mmu_load_to_nozzle_menu()"    if (degHotend0() > EXTRUDE_MINTEMP)    {        LcdUpdateDisabler disableLcdUpdate;        lcd_clear();        lcd_set_cursor(0, 1); lcd_puts_P(_i("Cutting filament")); //// c=18 r=1        lcd_print(" ");        lcd_print(filament_nr + 1);        mmu_filament_ramming();        mmu_command(MmuCmd::K0 + filament_nr);        manage_response(false, false, MMU_UNLOAD_MOVE);    }    else    {        show_preheat_nozzle_warning();    }}#endif //MMU_HAS_CUTTERvoid mmu_eject_filament(uint8_t filament, bool recover){//-//bFilamentAction=false;                            // NOT in "mmu_fil_eject_menu()"	if (filament < 5) 	{		if (degHotend0() > EXTRUDE_MINTEMP)		{			st_synchronize();			{			    LcdUpdateDisabler disableLcdUpdate;                lcd_clear();                lcd_set_cursor(0, 1); lcd_puts_P(_i("Ejecting filament"));                mmu_filament_ramming();                mmu_command(MmuCmd::E0 + filament);                manage_response(false, false, MMU_UNLOAD_MOVE);                if (recover)                {                    lcd_show_fullscreen_message_and_wait_P(_i("Please remove filament and then press the knob."));                    mmu_command(MmuCmd::R0);                    manage_response(false, false);                }            }		}		else		{			show_preheat_nozzle_warning();		}	}	else	{		puts_P(PSTR("Filament nr out of range!"));	}}//! @brief load more//!//! Try to feed more filament from MMU if it is not detected by filament sensor.//! Move filament back and forth to nozzle in order to detect jam.//! If PTFE tube is jammed, this cause filament to be unloaded and no longer//! detected by pulley IR sensor in next step.static void load_more(){    for (uint8_t i = 0; i < MMU_IDLER_SENSOR_ATTEMPTS_NR; i++)    {        if (PIN_GET(IR_SENSOR_PIN) == 0) break;        DEBUG_PRINTF_P(PSTR("Additional load attempt nr. %d\n"), i);        mmu_command(MmuCmd::C0);        manage_response(true, true, MMU_LOAD_MOVE);    }    current_position[E_AXIS] += 60;    plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], MMU_LOAD_FEEDRATE, active_extruder);    current_position[E_AXIS] -= 58;    plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], MMU_LOAD_FEEDRATE, active_extruder);    st_synchronize();}static void increment_load_fail(){    uint8_t mmu_load_fail = eeprom_read_byte((uint8_t*)EEPROM_MMU_LOAD_FAIL);    uint16_t mmu_load_fail_tot = eeprom_read_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT);    if(mmu_load_fail < 255) eeprom_update_byte((uint8_t*)EEPROM_MMU_LOAD_FAIL, mmu_load_fail + 1);    if(mmu_load_fail_tot < 65535) eeprom_update_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT, mmu_load_fail_tot + 1);}//! @brief continue loading filament//! @par blocking//!  * true blocking - do not return until successful load//!  * false non-blocking - pause print and return on load failure//!//! @startuml//! [*] --> [*] : !ir_sensor_detected /\n send MmuCmd::C0//! [*] --> LoadMore//! LoadMore --> [*] : filament \ndetected//! LoadMore --> Retry : !filament detected /\n increment load fail//! Retry --> [*] : filament \ndetected//! Retry --> Unload : !filament \ndetected//! Unload --> [*] : non-blocking//! Unload --> Retry : button \nclicked//!//! Retry : Cut filament if enabled//! Retry : repeat last T-code//! Unload : unload filament//! Unload : pause print//! Unload : show error message//!//! @endumlvoid mmu_continue_loading(bool blocking){	if (!ir_sensor_detected)	{	    mmu_command(MmuCmd::C0);	    return;	}    load_more();    enum class Ls : uint_least8_t    {        Enter,        Retry,        Unload,    };    Ls state = Ls::Enter;    while (PIN_GET(IR_SENSOR_PIN) != 0)    {        switch (state)        {        case Ls::Enter:            increment_load_fail();            // no break        case Ls::Retry:#ifdef MMU_HAS_CUTTER            if (1 == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED))            {                mmu_command(MmuCmd::K0 + tmp_extruder);                manage_response(true, true, MMU_UNLOAD_MOVE);            }#endif //MMU_HAS_CUTTER            mmu_command(MmuCmd::T0 + tmp_extruder);            manage_response(true, true, MMU_TCODE_MOVE);            load_more();            state = Ls::Unload;            break;        case Ls::Unload:            stop_and_save_print_to_ram(0, 0);            //lift z            current_position[Z_AXIS] += Z_PAUSE_LIFT;            if (current_position[Z_AXIS] > Z_MAX_POS) current_position[Z_AXIS] = Z_MAX_POS;            plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 15, active_extruder);            st_synchronize();            //Move XY to side            current_position[X_AXIS] = X_PAUSE_POS;            current_position[Y_AXIS] = Y_PAUSE_POS;            plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], 50, active_extruder);            st_synchronize();            mmu_command(MmuCmd::U0);            manage_response(false, true, MMU_UNLOAD_MOVE);            setAllTargetHotends(0);            lcd_setstatuspgm(_i("MMU load failed     "));////c=20 r=1            if (blocking)            {                marlin_wait_for_click();                restore_print_from_ram_and_continue(0);                state = Ls::Retry;            }            else            {                mmu_fil_loaded = false; //so we can retry same T-code again                isPrintPaused = true;                mmu_command(MmuCmd::W0);                return;            }            break;        }    }}
 |