Power Loss Recovery with a UPS (#15943)
This commit is contained in:
		
				
					committed by
					
						 Scott Lahteine
						Scott Lahteine
					
				
			
			
				
	
			
			
			
						parent
						
							ab9f0f2c4f
						
					
				
				
					commit
					ab8b24fdba
				
			| @@ -960,6 +960,8 @@ | ||||
|    */ | ||||
|   //#define POWER_LOSS_RECOVERY | ||||
|   #if ENABLED(POWER_LOSS_RECOVERY) | ||||
|     //#define BACKUP_POWER_SUPPLY       // Backup power / UPS to move the steppers on power loss | ||||
|     //#define POWER_LOSS_ZRAISE       2 // (mm) Z axis raise on resume (on power loss with UPS) | ||||
|     //#define POWER_LOSS_PIN         44 // Pin to detect power loss | ||||
|     //#define POWER_LOSS_STATE     HIGH // State of pin indicating power loss | ||||
|     //#define POWER_LOSS_PULL           // Set pullup / pulldown as appropriate | ||||
|   | ||||
| @@ -29,32 +29,33 @@ | ||||
|  | ||||
| #include <libmaple/gpio.h> | ||||
|  | ||||
| #define READ(IO)              (PIN_MAP[IO].gpio_device->regs->IDR & (1U << PIN_MAP[IO].gpio_bit) ? HIGH : LOW) | ||||
| #define WRITE(IO,V)           (PIN_MAP[IO].gpio_device->regs->BSRR = (1U << PIN_MAP[IO].gpio_bit) << ((V) ? 0 : 16)) | ||||
| #define TOGGLE(IO)            (PIN_MAP[IO].gpio_device->regs->ODR = PIN_MAP[IO].gpio_device->regs->ODR ^ (1U << PIN_MAP[IO].gpio_bit)) | ||||
| #define READ(IO)                (PIN_MAP[IO].gpio_device->regs->IDR & (1U << PIN_MAP[IO].gpio_bit) ? HIGH : LOW) | ||||
| #define WRITE(IO,V)             (PIN_MAP[IO].gpio_device->regs->BSRR = (1U << PIN_MAP[IO].gpio_bit) << ((V) ? 0 : 16)) | ||||
| #define TOGGLE(IO)              (PIN_MAP[IO].gpio_device->regs->ODR = PIN_MAP[IO].gpio_device->regs->ODR ^ (1U << PIN_MAP[IO].gpio_bit)) | ||||
|  | ||||
| #define _GET_MODE(IO)         gpio_get_mode(PIN_MAP[IO].gpio_device, PIN_MAP[IO].gpio_bit) | ||||
| #define _SET_MODE(IO,M)       gpio_set_mode(PIN_MAP[IO].gpio_device, PIN_MAP[IO].gpio_bit, M) | ||||
| #define _SET_OUTPUT(IO)       _SET_MODE(IO, GPIO_OUTPUT_PP) | ||||
| #define _SET_OUTPUT_OD(IO)    _SET_MODE(IO, GPIO_OUTPUT_OD) | ||||
| #define _GET_MODE(IO)           gpio_get_mode(PIN_MAP[IO].gpio_device, PIN_MAP[IO].gpio_bit) | ||||
| #define _SET_MODE(IO,M)         gpio_set_mode(PIN_MAP[IO].gpio_device, PIN_MAP[IO].gpio_bit, M) | ||||
| #define _SET_OUTPUT(IO)         _SET_MODE(IO, GPIO_OUTPUT_PP) | ||||
| #define _SET_OUTPUT_OD(IO)      _SET_MODE(IO, GPIO_OUTPUT_OD) | ||||
|  | ||||
| #define OUT_WRITE(IO,V)       do{ _SET_OUTPUT(IO); WRITE(IO,V); }while(0) | ||||
| #define OUT_WRITE_OD(IO,V)    do{ _SET_OUTPUT_OD(IO); WRITE(IO,V); }while(0) | ||||
| #define OUT_WRITE(IO,V)         do{ _SET_OUTPUT(IO); WRITE(IO,V); }while(0) | ||||
| #define OUT_WRITE_OD(IO,V)      do{ _SET_OUTPUT_OD(IO); WRITE(IO,V); }while(0) | ||||
|  | ||||
| #define SET_INPUT(IO)         _SET_MODE(IO, GPIO_INPUT_FLOATING) | ||||
| #define SET_INPUT_PULLUP(IO)  _SET_MODE(IO, GPIO_INPUT_PU) | ||||
| #define SET_OUTPUT(IO)        OUT_WRITE(IO, LOW) | ||||
| #define SET_PWM(IO)           pinMode(IO, PWM)    // do{ gpio_set_mode(PIN_MAP[pin].gpio_device, PIN_MAP[pin].gpio_bit, GPIO_AF_OUTPUT_PP); timer_set_mode(PIN_MAP[pin].timer_device, PIN_MAP[pin].timer_channel, TIMER_PWM); }while(0) | ||||
| #define SET_PWM_OD(IO)        pinMode(IO, PWM_OPEN_DRAIN) | ||||
| #define SET_INPUT(IO)           _SET_MODE(IO, GPIO_INPUT_FLOATING) | ||||
| #define SET_INPUT_PULLUP(IO)    _SET_MODE(IO, GPIO_INPUT_PU) | ||||
| #define SET_INPUT_PULLDOWN(IO)  _SET_MODE(IO, GPIO_INPUT_PD) | ||||
| #define SET_OUTPUT(IO)          OUT_WRITE(IO, LOW) | ||||
| #define SET_PWM(IO)             pinMode(IO, PWM)    // do{ gpio_set_mode(PIN_MAP[pin].gpio_device, PIN_MAP[pin].gpio_bit, GPIO_AF_OUTPUT_PP); timer_set_mode(PIN_MAP[pin].timer_device, PIN_MAP[pin].timer_channel, TIMER_PWM); }while(0) | ||||
| #define SET_PWM_OD(IO)          pinMode(IO, PWM_OPEN_DRAIN) | ||||
|  | ||||
| #define IS_INPUT(IO)          (_GET_MODE(IO) == GPIO_INPUT_FLOATING || _GET_MODE(IO) == GPIO_INPUT_ANALOG || _GET_MODE(IO) == GPIO_INPUT_PU || _GET_MODE(IO) == GPIO_INPUT_PD) | ||||
| #define IS_OUTPUT(IO)         (_GET_MODE(IO) == GPIO_OUTPUT_PP || _GET_MODE(IO) == GPIO_OUTPUT_OD) | ||||
| #define IS_INPUT(IO)            (_GET_MODE(IO) == GPIO_INPUT_FLOATING || _GET_MODE(IO) == GPIO_INPUT_ANALOG || _GET_MODE(IO) == GPIO_INPUT_PU || _GET_MODE(IO) == GPIO_INPUT_PD) | ||||
| #define IS_OUTPUT(IO)           (_GET_MODE(IO) == GPIO_OUTPUT_PP || _GET_MODE(IO) == GPIO_OUTPUT_OD) | ||||
|  | ||||
| #define PWM_PIN(IO)           (PIN_MAP[IO].timer_device != nullptr) | ||||
| #define PWM_PIN(IO)             (PIN_MAP[IO].timer_device != nullptr) | ||||
|  | ||||
| // digitalRead/Write wrappers | ||||
| #define extDigitalRead(IO)    digitalRead(IO) | ||||
| #define extDigitalWrite(IO,V) digitalWrite(IO,V) | ||||
| #define extDigitalRead(IO)      digitalRead(IO) | ||||
| #define extDigitalWrite(IO,V)   digitalWrite(IO,V) | ||||
|  | ||||
| // | ||||
| // Pins Definitions | ||||
|   | ||||
| @@ -65,6 +65,9 @@ PrintJobRecovery recovery; | ||||
| #ifndef POWER_LOSS_RETRACT_LEN | ||||
|   #define POWER_LOSS_RETRACT_LEN 0 | ||||
| #endif | ||||
| #ifndef POWER_LOSS_ZRAISE | ||||
|   #define POWER_LOSS_ZRAISE 2 | ||||
| #endif | ||||
|  | ||||
| /** | ||||
|  * Clear the recovery info | ||||
| @@ -235,10 +238,34 @@ void PrintJobRecovery::save(const bool force/*=false*/, const bool save_queue/*= | ||||
| } | ||||
|  | ||||
| #if PIN_EXISTS(POWER_LOSS) | ||||
|  | ||||
|   void PrintJobRecovery::_outage() { | ||||
|     save(true); | ||||
|     #if ENABLED(BACKUP_POWER_SUPPLY) | ||||
|       static bool lock = false; | ||||
|       if (lock) return; // No re-entrance from idle() during raise_z() | ||||
|       lock = true; | ||||
|     #endif | ||||
|     if (IS_SD_PRINTING()) save(true); | ||||
|     #if ENABLED(BACKUP_POWER_SUPPLY) | ||||
|       raise_z(); | ||||
|     #endif | ||||
|  | ||||
|     kill(GET_TEXT(MSG_OUTAGE_RECOVERY)); | ||||
|   } | ||||
|  | ||||
|   #if ENABLED(BACKUP_POWER_SUPPLY) | ||||
|  | ||||
|     void PrintJobRecovery::raise_z() { | ||||
|       // Disable all heaters to reduce power loss | ||||
|       thermalManager.disable_all_heaters(); | ||||
|       quickstop_stepper(); | ||||
|       // Raise Z axis | ||||
|       gcode.process_subcommands_now_P(PSTR("G91\nG0 Z" STRINGIFY(POWER_LOSS_ZRAISE))); | ||||
|       planner.synchronize(); | ||||
|     } | ||||
|  | ||||
|   #endif | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /** | ||||
| @@ -260,8 +287,6 @@ void PrintJobRecovery::write() { | ||||
|  */ | ||||
| void PrintJobRecovery::resume() { | ||||
|  | ||||
|   #define RECOVERY_ZRAISE 2 | ||||
|  | ||||
|   const uint32_t resume_sdpos = info.sdpos; // Get here before the stepper ISR overwrites it | ||||
|  | ||||
|   #if HAS_LEVELING | ||||
| @@ -272,15 +297,27 @@ void PrintJobRecovery::resume() { | ||||
|   // Reset E, raise Z, home XY... | ||||
|   gcode.process_subcommands_now_P(PSTR("G92.9 E0" | ||||
|     #if Z_HOME_DIR > 0 | ||||
|       // If Z homing goes to max, reset E and home all | ||||
|       "\nG28R0" | ||||
|  | ||||
|       // If Z homing goes to max, just reset E and home all | ||||
|       "\n" | ||||
|       "G28R0" | ||||
|       #if ENABLED(MARLIN_DEV_MODE) | ||||
|         "S" | ||||
|       #endif | ||||
|     #else | ||||
|  | ||||
|     #else // "G92.9 E0 ..." | ||||
|  | ||||
|       // Set Z to 0, raise Z by RECOVERY_ZRAISE, and Home (XY only for Cartesian) | ||||
|       // with no raise. (Only do simulated homing in Marlin Dev Mode.) | ||||
|       "Z0\nG1Z" STRINGIFY(RECOVERY_ZRAISE) "\nG28R0" | ||||
|       #if ENABLED(BACKUP_POWER_SUPPLY) | ||||
|         "Z" STRINGIFY(POWER_LOSS_ZRAISE)    // Z-axis was already raised at outage | ||||
|       #else | ||||
|         "Z0\n"                              // Set Z=0 | ||||
|         "G1Z" STRINGIFY(POWER_LOSS_ZRAISE)  // Raise Z | ||||
|       #endif | ||||
|       "\n" | ||||
|  | ||||
|       "G28R0" | ||||
|       #if ENABLED(MARLIN_DEV_MODE) | ||||
|         "S" | ||||
|       #elif !IS_KINEMATIC | ||||
|   | ||||
| @@ -168,7 +168,7 @@ class PrintJobRecovery { | ||||
|  | ||||
|   #if PIN_EXISTS(POWER_LOSS) | ||||
|     static inline void outage() { | ||||
|       if (enabled && IS_SD_PRINTING() && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE) | ||||
|       if (enabled && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE) | ||||
|         _outage(); | ||||
|     } | ||||
|   #endif | ||||
| @@ -184,6 +184,10 @@ class PrintJobRecovery { | ||||
|   private: | ||||
|     static void write(); | ||||
|  | ||||
|   #if ENABLED(BACKUP_POWER_SUPPLY) | ||||
|     static void raise_z(); | ||||
|   #endif | ||||
|  | ||||
|   #if PIN_EXISTS(POWER_LOSS) | ||||
|     static void _outage(); | ||||
|   #endif | ||||
|   | ||||
| @@ -52,8 +52,13 @@ inline void plr_error(PGM_P const prefix) { | ||||
| void GcodeSuite::M1000() { | ||||
|  | ||||
|   if (recovery.valid()) { | ||||
|     if (parser.seen('S')) | ||||
|       ui.goto_screen(menu_job_recovery); | ||||
|     if (parser.seen('S')) { | ||||
|       #if HAS_LCD_MENU | ||||
|         ui.goto_screen(menu_job_recovery); | ||||
|       #else | ||||
|         SERIAL_ECHO_MSG("Resume requires LCD."); | ||||
|       #endif | ||||
|     } | ||||
|     else | ||||
|       recovery.resume(); | ||||
|   } | ||||
|   | ||||
| @@ -2336,8 +2336,8 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) | ||||
|   #endif | ||||
| #endif | ||||
|  | ||||
| #if ENABLED(POWER_LOSS_RECOVERY) && DISABLED(ULTIPANEL) | ||||
|   #error "POWER_LOSS_RECOVERY currently requires an LCD Controller." | ||||
| #if ENABLED(BACKUP_POWER_SUPPLY) && !PIN_EXISTS(POWER_LOSS) | ||||
|   #error "BACKUP_POWER_SUPPLY requires a POWER_LOSS_PIN." | ||||
| #endif | ||||
|  | ||||
| #if ENABLED(Z_STEPPER_AUTO_ALIGN) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user