Quellcode durchsuchen

Merge pull request #3157 from wavexx/improve_d2

Add extra debugging functions & crash analysis
DRracer vor 3 Jahren
Ursprung
Commit
6188870c2e

+ 198 - 117
Firmware/Dcodes.cpp

@@ -23,28 +23,25 @@ void print_hex_byte(uint8_t val)
 	print_hex_nibble(val & 15);
 }
 
-void print_hex_word(uint16_t val)
+// debug range address type (fits all SRAM/PROGMEM/XFLASH memory ranges)
+#if defined(DEBUG_DCODE6) || defined(DEBUG_DCODES) || defined(XFLASH_DUMP)
+#include "xflash.h"
+#include "xflash_layout.h"
+
+#define DADDR_SIZE 32
+typedef uint32_t daddr_t; // XFLASH requires 24 bits
+#else
+#define DADDR_SIZE 16
+typedef uint16_t daddr_t;
+#endif
+
+void print_hex_word(daddr_t val)
 {
-	print_hex_byte(val >> 8);
-	print_hex_byte(val & 255);
-}
-
-void print_eeprom(uint16_t address, uint16_t count, uint8_t countperline = 16)
-{
-	while (count)
-	{
-		print_hex_word(address);
-		putchar(' ');
-		uint8_t count_line = countperline;
-		while (count && count_line)
-		{
-			putchar(' ');
-			print_hex_byte(eeprom_read_byte((uint8_t*)address++));
-			count_line--;
-			count--;
-		}
-		putchar('\n');
-	}
+#if DADDR_SIZE > 16
+    print_hex_byte((val >> 16) & 0xFF);
+#endif
+    print_hex_byte((val >> 8) & 0xFF);
+    print_hex_byte(val & 0xFF);
 }
 
 int parse_hex(char* hex, uint8_t* data, int count)
@@ -71,12 +68,16 @@ int parse_hex(char* hex, uint8_t* data, int count)
 }
 
 
-void print_mem(uint32_t address, uint16_t count, uint8_t type, uint8_t countperline = 16)
+enum class dcode_mem_t:uint8_t { sram, eeprom, progmem, xflash };
+
+void print_mem(daddr_t address, daddr_t count, dcode_mem_t type, uint8_t countperline = 16)
 {
+#if defined(DEBUG_DCODE6) || defined(DEBUG_DCODES) || defined(XFLASH_DUMP)
+    if(type == dcode_mem_t::xflash)
+        XFLASH_SPI_ENTER();
+#endif
 	while (count)
 	{
-		if (type == 2)
-			print_hex_nibble(address >> 16);
 		print_hex_word(address);
 		putchar(' ');
 		uint8_t count_line = countperline;
@@ -85,19 +86,75 @@ void print_mem(uint32_t address, uint16_t count, uint8_t type, uint8_t countperl
 			uint8_t data = 0;
 			switch (type)
 			{
-			case 0: data = *((uint8_t*)address++); break;
-			case 1: data = eeprom_read_byte((uint8_t*)address++); break;
-			case 2: data = pgm_read_byte_far((uint8_t*)address++); break;
+			case dcode_mem_t::sram: data = *((uint8_t*)address); break;
+			case dcode_mem_t::eeprom: data = eeprom_read_byte((uint8_t*)address); break;
+			case dcode_mem_t::progmem: break;
+#if defined(DEBUG_DCODE6) || defined(DEBUG_DCODES) || defined(XFLASH_DUMP)
+            case dcode_mem_t::xflash: xflash_rd_data(address, &data, 1); break;
+#else
+            case dcode_mem_t::xflash: break;
+#endif
 			}
+            ++address;
 			putchar(' ');
 			print_hex_byte(data);
 			count_line--;
 			count--;
+
+            // sporadically call manage_heater, but only when interrupts are enabled (meaning
+            // print_mem is called by D2). Don't do anything otherwise: we are inside a crash
+            // handler where memory & stack needs to be preserved!
+            if((SREG & (1 << SREG_I)) && !((uint16_t)count % 8192))
+                manage_heater();
 		}
 		putchar('\n');
 	}
 }
 
+// TODO: this only handles SRAM/EEPROM 16bit addresses
+void write_mem(uint16_t address, uint16_t count, const uint8_t* data, const dcode_mem_t type)
+{
+    for (uint16_t i = 0; i < count; i++)
+    {
+        switch (type)
+        {
+        case dcode_mem_t::sram: *((uint8_t*)address) = data[i]; break;
+        case dcode_mem_t::eeprom: eeprom_write_byte((uint8_t*)address, data[i]); break;
+        case dcode_mem_t::progmem: break;
+        case dcode_mem_t::xflash: break;
+        }
+        ++address;
+    }
+}
+
+void dcode_core(daddr_t addr_start, const daddr_t addr_end, const dcode_mem_t type,
+                uint8_t dcode, const char* type_desc)
+{
+    KEEPALIVE_STATE(NOT_BUSY);
+    DBG(_N("D%d - Read/Write %S\n"), dcode, type_desc);
+    daddr_t count = -1; // RW the entire space by default
+    if (code_seen('A'))
+        addr_start = (strchr_pointer[1] == 'x')?strtol(strchr_pointer + 2, 0, 16):(int)code_value();
+    if (code_seen('C'))
+        count = code_value_long();
+    if (addr_start > addr_end)
+        addr_start = addr_end;
+    if ((addr_start + count) > addr_end || (addr_start + count) < addr_start)
+        count = addr_end - addr_start;
+    if (code_seen('X'))
+    {
+        uint8_t data[16];
+        count = parse_hex(strchr_pointer + 1, data, 16);
+        write_mem(addr_start, count, data, type);
+#if DADDR_SIZE > 16
+        DBG(_N("%lu bytes written to %S at address 0x%04lx\n"), count, type_desc, addr_start);
+#else
+        DBG(_N("%u bytes written to %S at address 0x%08x\n"), count, type_desc, addr_start);
+#endif
+    }
+    print_mem(addr_start, count, type);
+}
+
 #if defined DEBUG_DCODE3 || defined DEBUG_DCODES
 #define EEPROM_SIZE 0x1000
     /*!
@@ -120,46 +177,7 @@ void print_mem(uint32_t address, uint16_t count, uint8_t type, uint8_t countperl
     */
 void dcode_3()
 {
-	DBG(_N("D3 - Read/Write EEPROM\n"));
-	uint16_t address = 0x0000; //default 0x0000
-	uint16_t count = EEPROM_SIZE; //default 0x1000 (entire eeprom)
-	if (code_seen('A')) // Address (0x0000-0x0fff)
-		address = (strchr_pointer[1] == 'x')?strtol(strchr_pointer + 2, 0, 16):(int)code_value();
-	if (code_seen('C')) // Count (0x0001-0x1000)
-		count = (int)code_value();
-	address &= 0x1fff;
-	if (count > EEPROM_SIZE) count = EEPROM_SIZE;
-	if ((address + count) > EEPROM_SIZE) count = EEPROM_SIZE - address;
-	if (code_seen('X')) // Data
-	{
-		uint8_t data[16];
-		count = parse_hex(strchr_pointer + 1, data, 16);
-		if (count > 0)
-		{
-			for (uint16_t i = 0; i < count; i++)
-				eeprom_write_byte((uint8_t*)(address + i), data[i]);
-			printf_P(_N("%d bytes written to EEPROM at address 0x%04x"), count, address);
-			putchar('\n');
-		}
-		else
-			count = 0;
-	}
-	print_mem(address, count, 1);
-/*	while (count)
-	{
-		print_hex_word(address);
-		putchar(' ');
-		uint8_t countperline = 16;
-		while (count && countperline)
-		{
-			uint8_t data = eeprom_read_byte((uint8_t*)address++);
-			putchar(' ');
-			print_hex_byte(data);
-			countperline--;
-			count--;
-		}
-		putchar('\n');
-	}*/
+    dcode_core(0, EEPROM_SIZE, dcode_mem_t::eeprom, 3, _N("EEPROM"));
 }
 #endif //DEBUG_DCODE3
 
@@ -239,68 +257,34 @@ void dcode_1()
 		eeprom_write_byte((unsigned char*)i, (unsigned char)0xff);
 	softReset();
 }
+#endif
 
+#if defined DEBUG_DCODE2 || defined DEBUG_DCODES
     /*!
     ### D2 - Read/Write RAM <a href="https://reprap.org/wiki/G-code#D2:_Read.2FWrite_RAM">D3: Read/Write RAM</a>
     This command can be used without any additional parameters. It will read the entire RAM.
     #### Usage
-    
+
         D2 [ A | C | X ]
-    
+
     #### Parameters
-    - `A` - Address (x0000-x1fff)
-    - `C` - Count (1-8192)
+    - `A` - Address (x0000-x21ff)
+    - `C` - Count (1-8704)
     - `X` - Data
 
 	#### Notes
 	- The hex address needs to be lowercase without the 0 before the x
-	- Count is decimal 
+	- Count is decimal
 	- The hex data needs to be lowercase
-	
+
     */
 void dcode_2()
 {
-	LOG("D2 - Read/Write RAM\n");
-	uint16_t address = 0x0000; //default 0x0000
-	uint16_t count = 0x2000; //default 0x2000 (entire ram)
-	if (code_seen('A')) // Address (0x0000-0x1fff)
-		address = (strchr_pointer[1] == 'x')?strtol(strchr_pointer + 2, 0, 16):(int)code_value();
-	if (code_seen('C')) // Count (0x0001-0x2000)
-		count = (int)code_value();
-	address &= 0x1fff;
-	if (count > 0x2000) count = 0x2000;
-	if ((address + count) > 0x2000) count = 0x2000 - address;
-	if (code_seen('X')) // Data
-	{
-		uint8_t data[16];
-		count = parse_hex(strchr_pointer + 1, data, 16);
-		if (count > 0)
-		{
-			for (uint16_t i = 0; i < count; i++)
-				*((uint8_t*)(address + i)) =  data[i];
-			LOG("%d bytes written to RAM at address %04x", count, address);
-		}
-		else
-			count = 0;
-	}
-	print_mem(address, count, 0);
-/*	while (count)
-	{
-		print_hex_word(address);
-		putchar(' ');
-		uint8_t countperline = 16;
-		while (count && countperline)
-		{
-			uint8_t data = *((uint8_t*)address++);
-			putchar(' ');
-			print_hex_byte(data);
-			countperline--;
-			count--;
-		}
-		putchar('\n');
-	}*/
+    dcode_core(RAMSTART, RAMEND+1, dcode_mem_t::sram, 2, _N("SRAM"));
 }
+#endif
 
+#ifdef DEBUG_DCODES
     /*!
     
     ### D4 - Read/Write PIN <a href="https://reprap.org/wiki/G-code#D4:_Read.2FWrite_PIN">D4: Read/Write PIN</a>
@@ -425,17 +409,32 @@ void dcode_5()
 }
 #endif //DEBUG_DCODE5
 
-#ifdef DEBUG_DCODES
-
+#if defined(XFLASH) && (defined DEBUG_DCODE6 || defined DEBUG_DCODES)
     /*!
     ### D6 - Read/Write external FLASH <a href="https://reprap.org/wiki/G-code#D6:_Read.2FWrite_external_FLASH">D6: Read/Write external Flash</a>
-    Reserved
+    This command can be used without any additional parameters. It will read the entire XFLASH.
+    #### Usage
+
+        D6 [ A | C | X ]
+
+    #### Parameters
+    - `A` - Address (x0000-x3ffff)
+    - `C` - Count (1-262144)
+    - `X` - Data
+
+	#### Notes
+	- The hex address needs to be lowercase without the 0 before the x
+	- Count is decimal
+	- The hex data needs to be lowercase
+	- Writing is currently not implemented
     */
 void dcode_6()
 {
-	LOG("D6 - Read/Write external FLASH\n");
+    dcode_core(0x0, XFLASH_SIZE, dcode_mem_t::xflash, 6, _N("XFLASH"));
 }
+#endif
 
+#ifdef DEBUG_DCODES
     /*!
     ### D7 - Read/Write Bootloader <a href="https://reprap.org/wiki/G-code#D7:_Read.2FWrite_Bootloader">D7: Read/Write Bootloader</a>
     Reserved
@@ -927,5 +926,87 @@ void dcode_9125()
 }
 #endif //PAT9125
 
-
 #endif //DEBUG_DCODES
+
+#ifdef XFLASH_DUMP
+#include "xflash_dump.h"
+
+void dcode_20()
+{
+    if(code_seen('E'))
+        xfdump_full_dump_and_reset();
+    else
+    {
+        unsigned long ts = _millis();
+        xfdump_dump();
+        ts = _millis() - ts;
+        DBG(_N("dump completed in %lums\n"), ts);
+    }
+}
+
+void dcode_21()
+{
+    if(!xfdump_check_state())
+        DBG(_N("no dump available\n"));
+    else
+    {
+        KEEPALIVE_STATE(NOT_BUSY);
+        DBG(_N("D21 - read crash dump\n"));
+        print_mem(DUMP_OFFSET, sizeof(dump_t), dcode_mem_t::xflash);
+    }
+}
+
+void dcode_22()
+{
+    if(!xfdump_check_state())
+        DBG(_N("no dump available\n"));
+    else
+    {
+        xfdump_reset();
+        DBG(_N("dump cleared\n"));
+    }
+}
+#endif
+
+#ifdef EMERGENCY_SERIAL_DUMP
+#include "asm.h"
+#include "xflash_dump.h"
+
+bool emergency_serial_dump = false;
+
+void __attribute__((noinline)) serial_dump_and_reset(dump_crash_reason reason)
+{
+    uint16_t sp;
+    uint32_t pc;
+
+    // we're being called from a live state, so shut off interrupts ...
+    cli();
+
+    // sample SP/PC
+    sp = SP;
+    GETPC(&pc);
+
+    // extend WDT long enough to allow writing the entire stream
+    wdt_enable(WDTO_8S);
+
+    // ... and heaters
+    WRITE(FAN_PIN, HIGH);
+    disable_heater();
+
+    // this function can also be called from within a corrupted state, so not use
+    // printf family of functions that use the heap or grow the stack.
+    SERIAL_ECHOLNPGM("D23 - emergency serial dump");
+    SERIAL_ECHOPGM("error: ");
+    MYSERIAL.print((uint8_t)reason, DEC);
+    SERIAL_ECHOPGM(" 0x");
+    MYSERIAL.print(pc, HEX);
+    SERIAL_ECHOPGM(" 0x");
+    MYSERIAL.println(sp, HEX);
+
+    print_mem(0, RAMEND+1, dcode_mem_t::sram);
+    SERIAL_ECHOLNRPGM(MSG_OK);
+
+    // reset soon
+    softReset();
+}
+#endif

+ 18 - 0
Firmware/Dcodes.h

@@ -4,7 +4,10 @@
 extern void dcode__1(); //D-1 - Endless loop (to simulate deadlock)
 extern void dcode_0(); //D0 - Reset
 extern void dcode_1(); //D1 - Clear EEPROM
+
+#if defined DEBUG_DCODE2 || defined DEBUG_DCODES
 extern void dcode_2(); //D2 - Read/Write RAM
+#endif
 
 #if defined DEBUG_DCODE3 || defined DEBUG_DCODES
 extern void dcode_3(); //D3 - Read/Write EEPROM
@@ -16,13 +19,28 @@ extern void dcode_4(); //D4 - Read/Write PIN
 extern void dcode_5(); //D5 - Read/Write FLASH
 #endif //DEBUG_DCODE5
 
+#if defined DEBUG_DCODE6 || defined DEBUG_DCODES
 extern void dcode_6(); //D6 - Read/Write external FLASH
+#endif
+
 extern void dcode_7(); //D7 - Read/Write Bootloader
 extern void dcode_8(); //D8 - Read/Write PINDA
 extern void dcode_9(); //D9 - Read/Write ADC (Write=enable simulated, Read=disable simulated)
 extern void dcode_10(); //D10 - XYZ calibration = OK
 extern void dcode_12(); //D12 - Log time. Writes the current time in the log file.
 
+#ifdef XFLASH_DUMP
+extern void dcode_20(); //D20 - Generate an offline crash dump
+extern void dcode_21(); //D21 - Print crash dump to serial
+extern void dcode_22(); //D22 - Clear crash dump state
+#endif
+
+#ifdef EMERGENCY_SERIAL_DUMP
+#include "xflash_dump.h"
+extern bool emergency_serial_dump;
+extern void serial_dump_and_reset(dump_crash_reason);
+#endif
+
 #ifdef HEATBED_ANALYSIS
 extern void dcode_80(); //D80 - Bed check. This command will log data to SD card file "mesh.txt".
 extern void dcode_81(); //D81 - Bed analysis. This command will log data to SD card file "wldsd.txt".

+ 1 - 0
Firmware/Marlin.h

@@ -497,6 +497,7 @@ void marlin_wait_for_click();
 void raise_z_above(float target, bool plan=true);
 
 extern "C" void softReset();
+void stack_error();
 
 extern uint32_t IP_address;
 

+ 163 - 2
Firmware/Marlin_main.cpp

@@ -66,6 +66,7 @@
 
 #include "menu.h"
 #include "ultralcd.h"
+#include "conv2str.h"
 #include "backlight.h"
 
 #include "planner.h"
@@ -108,6 +109,8 @@
 #include "optiboot_xflash.h"
 #endif //XFLASH
 
+#include "xflash_dump.h"
+
 #ifdef BLINKM
 #include "BlinkM.h"
 #include "Wire.h"
@@ -994,6 +997,58 @@ void list_sec_lang_from_external_flash()
 #endif //(LANG_MODE != 0)
 
 
+static void fw_crash_init()
+{
+#ifdef XFLASH_DUMP
+    dump_crash_reason crash_reason;
+    if(xfdump_check_state(&crash_reason))
+    {
+        // always signal to the host that a dump is available for retrieval
+        puts_P(_N("// action:dump_available"));
+
+#ifdef EMERGENCY_DUMP
+        if(crash_reason != dump_crash_reason::manual &&
+           eeprom_read_byte((uint8_t*)EEPROM_FW_CRASH_FLAG) != 0xFF)
+        {
+            lcd_show_fullscreen_message_and_wait_P(
+                    _i("FIRMWARE CRASH!\n"
+                       "Debug data available for analysis. "
+                       "Contact support to submit details."));
+        }
+#endif
+    }
+#else //XFLASH_DUMP
+    dump_crash_reason crash_reason = (dump_crash_reason)eeprom_read_byte((uint8_t*)EEPROM_FW_CRASH_FLAG);
+    if(crash_reason != dump_crash_reason::manual && (uint8_t)crash_reason != 0xFF)
+    {
+        lcd_beeper_quick_feedback();
+        lcd_clear();
+
+        lcd_puts_P(_i("FIRMWARE CRASH!\nCrash reason:\n"));
+        switch(crash_reason)
+        {
+        case dump_crash_reason::stack_error:
+            lcd_puts_P(_i("Static memory has\nbeen overwritten"));
+            break;
+        case dump_crash_reason::watchdog:
+            lcd_puts_P(_i("Watchdog timeout"));
+            break;
+        case dump_crash_reason::bad_isr:
+            lcd_puts_P(_i("Bad interrupt"));
+            break;
+        default:
+            lcd_print((uint8_t)crash_reason);
+            break;
+        }
+        lcd_wait_for_click();
+    }
+#endif //XFLASH_DUMP
+
+    // prevent crash prompts to reappear once acknowledged
+    eeprom_update_byte((uint8_t*)EEPROM_FW_CRASH_FLAG, 0xFF);
+}
+
+
 static void xflash_err_msg()
 {
 	lcd_clear();
@@ -1612,6 +1667,9 @@ void setup()
 	if (tmc2130_home_enabled == 0xff) tmc2130_home_enabled = 0;
 #endif //TMC2130
 
+    // report crash failures
+    fw_crash_init();
+
 #ifdef UVLO_SUPPORT
   if (eeprom_read_byte((uint8_t*)EEPROM_UVLO) != 0) { //previous print was terminated by UVLO
 /*
@@ -1657,10 +1715,45 @@ void setup()
   KEEPALIVE_STATE(NOT_BUSY);
 #ifdef WATCHDOG
   wdt_enable(WDTO_4S);
+#ifdef EMERGENCY_HANDLERS
+  WDTCSR |= (1 << WDIE);
+#endif //EMERGENCY_HANDLERS
 #endif //WATCHDOG
 }
 
 
+static inline void crash_and_burn(dump_crash_reason reason)
+{
+    WRITE(BEEPER, HIGH);
+    eeprom_update_byte((uint8_t*)EEPROM_FW_CRASH_FLAG, (uint8_t)reason);
+#ifdef EMERGENCY_DUMP
+    xfdump_full_dump_and_reset(reason);
+#elif defined(EMERGENCY_SERIAL_DUMP)
+    if(emergency_serial_dump)
+        serial_dump_and_reset(reason);
+#endif
+    softReset();
+}
+
+#ifdef EMERGENCY_HANDLERS
+#ifdef WATCHDOG
+ISR(WDT_vect)
+{
+    crash_and_burn(dump_crash_reason::watchdog);
+}
+#endif
+
+ISR(BADISR_vect)
+{
+    crash_and_burn(dump_crash_reason::bad_isr);
+}
+#endif //EMERGENCY_HANDLERS
+
+void stack_error() {
+    crash_and_burn(dump_crash_reason::stack_error);
+}
+
+
 void trace();
 
 #define CHUNK_SIZE 64 // bytes
@@ -9080,7 +9173,9 @@ Sigma_Exit:
     */
 	case 1:
 		dcode_1(); break;
+#endif
 
+#if defined DEBUG_DCODE2 || defined DEBUG_DCODES
     /*!
     ### D2 - Read/Write RAM <a href="https://reprap.org/wiki/G-code#D2:_Read.2FWrite_RAM">D3: Read/Write RAM</a>
     This command can be used without any additional parameters. It will read the entire RAM.
@@ -9167,7 +9262,7 @@ Sigma_Exit:
 	case 5:
 		dcode_5(); break;
 #endif //DEBUG_DCODE5
-#ifdef DEBUG_DCODES
+#if defined DEBUG_DCODE6 || defined DEBUG_DCODES
 
     /*!
     ### D6 - Read/Write external FLASH <a href="https://reprap.org/wiki/G-code#D6:_Read.2FWrite_external_FLASH">D6: Read/Write external Flash</a>
@@ -9175,6 +9270,8 @@ Sigma_Exit:
     */
 	case 6:
 		dcode_6(); break;
+#endif
+#ifdef DEBUG_DCODES
 
     /*!
     ### D7 - Read/Write Bootloader <a href="https://reprap.org/wiki/G-code#D7:_Read.2FWrite_Bootloader">D7: Read/Write Bootloader</a>
@@ -9228,8 +9325,72 @@ Sigma_Exit:
     ### D12 - Time <a href="https://reprap.org/wiki/G-code#D12:_Time">D12: Time</a>
     Writes the current time in the log file.
     */
-
 #endif //DEBUG_DCODES
+
+#ifdef XFLASH_DUMP
+    /*!
+    ### D20 - Generate an offline crash dump
+    Generate a crash dump for later retrival.
+    #### Usage
+
+     D20 [E]
+
+    ### Parameters
+    - `E` - Perform an emergency crash dump (resets the printer).
+    ### Notes
+    - A crash dump can be later recovered with D21, or cleared with D22.
+    - An emergency crash dump includes register data, but will cause the printer to reset after the dump
+      is completed.
+    */
+    case 20: {
+        dcode_20();
+        break;
+    };
+
+    /*!
+    ### D21 - Print crash dump to serial
+    Output the complete crash dump (if present) to the serial.
+    #### Usage
+
+     D21
+
+    ### Notes
+    - The starting address can vary between builds, but it's always at the beginning of the data section.
+    */
+    case 21: {
+        dcode_21();
+        break;
+    };
+
+    /*!
+    ### D22 - Clear crash dump state
+    Clear an existing internal crash dump.
+    #### Usage
+
+     D22
+    */
+    case 22: {
+        dcode_22();
+        break;
+    };
+#endif //XFLASH_DUMP
+
+#ifdef EMERGENCY_SERIAL_DUMP
+    /*!
+    ### D23 - Request emergency dump on serial
+    On boards without offline dump support, request online dumps to the serial port on firmware faults.
+    When online dumps are enabled, the FW will dump memory on the serial before resetting.
+    #### Usage
+
+     D23 [R]
+    #### Parameters
+    - `R` - Disable online dumps.
+    */
+    case 23: {
+        emergency_serial_dump = !code_seen('R');
+    };
+#endif
+
 #ifdef HEATBED_ANALYSIS
 
     /*!

+ 2 - 10
Firmware/SdFatUtil.cpp

@@ -48,24 +48,16 @@ void SdFatUtil::set_stack_guard()
 {	
 	uint32_t *stack_guard;
 
-	stack_guard = (uint32_t*)&__bss_end;
+	stack_guard = (uint32_t*)(&__bss_end + STACK_GUARD_MARGIN);
     *stack_guard = STACK_GUARD_TEST_VALUE;
 }
 
 bool SdFatUtil::test_stack_integrity()
 {
-	uint32_t* stack_guard = (uint32_t*)&__bss_end;
+	uint32_t* stack_guard = (uint32_t*)(&__bss_end + STACK_GUARD_MARGIN);
 	return (*stack_guard == STACK_GUARD_TEST_VALUE);
 }
 
-uint32_t SdFatUtil::get_stack_guard_test_value()
-{
-	uint32_t* stack_guard;
-	uint32_t output;
-	stack_guard = (uint32_t*)&__bss_end;
-	output = *stack_guard;
-	return(output);
-}
 //------------------------------------------------------------------------------
 /** %Print a string in flash memory.
  *

+ 1 - 2
Firmware/SdFatUtil.h

@@ -41,11 +41,10 @@ namespace SdFatUtil {
   void SerialPrintln_P(PGM_P str);
   void set_stack_guard();
   bool test_stack_integrity();
-  uint32_t get_stack_guard_test_value();
 }
 
 using namespace SdFatUtil;  // NOLINT
 #endif  // #define SdFatUtil_h
 
 
-#endif
+#endif

+ 24 - 0
Firmware/asm.h

@@ -0,0 +1,24 @@
+#pragma once
+#include <stdint.h>
+
+#ifdef __AVR_ATmega2560__
+
+// return the current PC (on AVRs with 22bit PC)
+static inline void GETPC(uint32_t* v)
+{
+  uint8_t a, b, c;
+  asm
+  (
+      "rcall .\n"
+      "pop %2\n"
+      "pop %1\n"
+      "pop %0\n"
+      : "=r" (a), "=r" (b), "=r" (c)
+  );
+  ((uint8_t*)v)[0] = a;
+  ((uint8_t*)v)[1] = b;
+  ((uint8_t*)v)[2] = c;
+  ((uint8_t*)v)[3] = 0;
+}
+
+#endif

+ 2 - 1
Firmware/bootapp.h

@@ -3,10 +3,11 @@
 #define BOOTAPP_H
 
 #include "config.h"
+#include <avr/io.h>
 #include <inttypes.h>
 
 
-#define RAMSIZE        0x2000
+#define RAMSIZE        (RAMEND+1-RAMSTART)
 #define boot_src_addr  (*((uint32_t*)(RAMSIZE - 16)))
 #define boot_dst_addr  (*((uint32_t*)(RAMSIZE - 12)))
 #define boot_copy_size (*((uint16_t*)(RAMSIZE - 8)))

+ 2 - 0
Firmware/cardreader.cpp

@@ -2,6 +2,8 @@
 #include "cmdqueue.h"
 #include "cardreader.h"
 #include "ultralcd.h"
+#include "conv2str.h"
+#include "menu.h"
 #include "stepper.h"
 #include "temperature.h"
 #include "language.h"

+ 21 - 0
Firmware/config.h

@@ -68,4 +68,25 @@
 #define COMMUNITY_LANG_SUPPORT
 #endif
 
+// Sanity checks for correct configuration of XFLASH_DUMP options
+#if defined(XFLASH_DUMP) && !defined(XFLASH)
+#error "XFLASH_DUMP requires XFLASH support"
+#endif
+#if (defined(MENU_DUMP) || defined(EMERGENCY_DUMP)) && !defined(XFLASH_DUMP)
+#error "MENU_DUMP and EMERGENCY_DUMP require XFLASH_DUMP"
+#endif
+
+// Support for serial dumps is mutually exclusive with XFLASH_DUMP features
+#if defined(EMERGENCY_DUMP) && defined(EMERGENCY_SERIAL_DUMP)
+#error "EMERGENCY_DUMP and EMERGENCY_SERIAL_DUMP are mutually exclusive"
+#endif
+#if defined(MENU_DUMP) && defined(MENU_SERIAL_DUMP)
+#error "MENU_DUMP and MENU_SERIAL_DUMP are mutually exclusive"
+#endif
+
+// Reduce internal duplication
+#if defined(EMERGENCY_DUMP) || defined(EMERGENCY_SERIAL_DUMP)
+#define EMERGENCY_HANDLERS
+#endif
+
 #endif //_CONFIG_H

+ 5 - 3
Firmware/eeprom.h

@@ -325,8 +325,9 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 | 0x0D0D 3341		| float		| EEPROM_UVLO_RETRACT_ACCELL			| ???			| ff ff ff ffh			| Power panic saved retract acceleration     		| ???			| D3 Ax0d0d C4
 | 0x0D09 3337		| float		| EEPROM_UVLO_TRAVEL_ACCELL				| ???			| ff ff ff ffh			| Power panic saved travel acceleration     		| ???			| D3 Ax0d09 C4
 | 0x0D05 3333		| uint32_t	| EEPROM_JOB_ID							| ???			| 00 00 00 00h			| Job ID used by host software						| D3 only		| D3 Ax0d05 C4
-| 0x0D01 3329		| uint8_t	| EEPROM_ECOOL_ENABLE					| ffh 255		| ^						| Disable extruder motor scaling for non-farm print	| LCD menu		| D3 Ax0d01 FF
-| ^					| ^			| ^										| 2ah 42		| ^						| Enable extruder motor scaling for non-farm print	| ^				| D3 Ax0d01 42
+| 0x0D04 3332		| uint8_t	| EEPROM_ECOOL_ENABLE					| ffh 255		| ^						| Disable extruder motor scaling for non-farm print	| LCD menu		| D3 Ax0d04 C1
+| ^					| ^			| ^										| 2ah 42		| ^						| Enable extruder motor scaling for non-farm print	| ^				| D3 Ax0d04 C1
+| 0x0D03 3321		| uint8_t	| EEPROM_FW_CRASH_FLAG					| 01h 1			| ff/00					| Last FW crash reason (dump_crash_reason)			| D21/D22		| D3 Ax0d03 C1
 
 | Address begin		| Bit/Type 	| Name 									| Valid values	| Default/FactoryReset	| Description 										| Gcode/Function| Debug code
 | :--:				| :--: 		| :--: 									| :--:			| :--:					| :--:												| :--:			| :--:
@@ -541,9 +542,10 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE);
 #define EEPROM_JOB_ID (EEPROM_UVLO_TRAVEL_ACCELL-4) //uint32_t
 
 #define EEPROM_ECOOL_ENABLE (EEPROM_JOB_ID-1) // uint8_t
+#define EEPROM_FW_CRASH_FLAG (EEPROM_ECOOL_ENABLE-1) // uint8_t
 
 //This is supposed to point to last item to allow EEPROM overrun check. Please update when adding new items.
-#define EEPROM_LAST_ITEM EEPROM_ECOOL_ENABLE
+#define EEPROM_LAST_ITEM EEPROM_FW_CRASH_FLAG
 // !!!!!
 // !!!!! this is end of EEPROM section ... all updates MUST BE inserted before this mark !!!!!
 // !!!!!

+ 4 - 3
Firmware/language.c

@@ -9,6 +9,7 @@
 
 #ifdef XFLASH
 #include "xflash.h"
+#include "xflash_layout.h"
 #endif //XFLASH
 
 // Currently active language selection.
@@ -110,7 +111,7 @@ uint8_t lang_get_count()
 #ifdef XFLASH
 	XFLASH_SPI_ENTER();
 	uint8_t count = 2; //count = 1+n (primary + secondary + all in xflash)
-	uint32_t addr = 0x00000; //start of xflash
+	uint32_t addr = LANG_OFFSET;
 	lang_table_header_t header; //table header structure
 	while (1)
 	{
@@ -143,7 +144,7 @@ uint8_t lang_get_header(uint8_t lang, lang_table_header_t* header, uint32_t* off
 		return (header->magic == LANG_MAGIC)?1:0; //return 1 if magic valid
 	}
 	XFLASH_SPI_ENTER();
-	uint32_t addr = 0x00000; //start of xflash
+	uint32_t addr = LANG_OFFSET;
 	lang--;
 	while (1)
 	{
@@ -176,7 +177,7 @@ uint16_t lang_get_code(uint8_t lang)
 		return pgm_read_word(((uint32_t*)(ui + 10))); //return lang code from progmem
 	}
 	XFLASH_SPI_ENTER();
-	uint32_t addr = 0x00000; //start of xflash
+	uint32_t addr = LANG_OFFSET;
 	lang_table_header_t header; //table header structure
 	lang--;
 	while (1)

+ 1 - 0
Firmware/mmu.cpp

@@ -11,6 +11,7 @@
 #include "cardreader.h"
 #include "cmdqueue.h"
 #include "ultralcd.h"
+#include "menu.h"
 #include "sound.h"
 #include "printers.h"
 #include <avr/pgmspace.h>

+ 6 - 1
Firmware/temperature.cpp

@@ -32,11 +32,13 @@
 #include "Marlin.h"
 #include "cmdqueue.h"
 #include "ultralcd.h"
+#include "menu.h"
+#include "conv2str.h"
 #include "sound.h"
 #include "temperature.h"
 #include "cardreader.h"
 
-#include "Sd2PinMap.h"
+#include "SdFatUtil.h"
 
 #include <avr/wdt.h>
 #include "adc.h"
@@ -2060,6 +2062,9 @@ FORCE_INLINE static void temperature_isr()
   }
 #endif //BABYSTEPPING
 
+  // Check if a stack overflow happened
+  if (!SdFatUtil::test_stack_integrity()) stack_error();
+
 #if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
   check_fans();
 #endif //(defined(TACH_0))

+ 69 - 10
Firmware/ultralcd.cpp

@@ -5,6 +5,7 @@
 
 #include "temperature.h"
 #include "ultralcd.h"
+#include "conv2str.h"
 #include "fsensor.h"
 #include "Marlin.h"
 #include "language.h"
@@ -28,8 +29,6 @@
 //#include "Configuration.h"
 #include "cmdqueue.h"
 
-#include "SdFatUtil.h"
-
 #ifdef FILAMENT_SENSOR
 #include "pat9125.h"
 #include "fsensor.h"
@@ -1806,6 +1805,61 @@ static void lcd_preheat_menu()
     lcd_generic_preheat_menu();
 }
 
+
+#ifdef MENU_DUMP
+#include "xflash_dump.h"
+
+static void lcd_dump_memory()
+{
+    lcd_beeper_quick_feedback();
+    xfdump_dump();
+    lcd_return_to_status();
+}
+#endif //MENU_DUMP
+#ifdef MENU_SERIAL_DUMP
+#include "Dcodes.h"
+
+static void lcd_serial_dump()
+{
+    serial_dump_and_reset(dump_crash_reason::manual);
+}
+#endif //MENU_SERIAL_DUMP
+
+#if defined(DEBUG_BUILD) && defined(EMERGENCY_HANDLERS)
+#include <avr/wdt.h>
+
+#ifdef WATCHDOG
+static void lcd_wdr_crash()
+{
+    while (1);
+}
+#endif
+
+static uint8_t lcd_stack_crash_(uint8_t arg, uint32_t sp = 0)
+{
+    // populate the stack with an increasing value for ease of testing
+    volatile uint16_t tmp __attribute__((unused)) = sp;
+
+    _delay(arg);
+    uint8_t ret = lcd_stack_crash_(arg, SP);
+
+    // required to avoid tail call elimination and to slow down the stack growth
+    _delay(ret);
+
+    return ret;
+}
+
+static void lcd_stack_crash()
+{
+#ifdef WATCHDOG
+    wdt_disable();
+#endif
+    // delay choosen in order to hit the stack-check in the temperature isr reliably
+    lcd_stack_crash_(10);
+}
+#endif
+
+
 //! @brief Show Support Menu
 //!
 //! @code{.unparsed}
@@ -1997,8 +2051,20 @@ static void lcd_support_menu()
   MENU_ITEM_SUBMENU_P(_i("Voltages"), lcd_menu_voltages);////MSG_MENU_VOLTAGES c=18
 #endif //defined VOLT_BED_PIN || defined VOLT_PWR_PIN
 
-
+#ifdef MENU_DUMP
+    MENU_ITEM_FUNCTION_P(_i("Dump memory"), lcd_dump_memory);
+#endif //MENU_DUMP
+#ifdef MENU_SERIAL_DUMP
+    if (emergency_serial_dump)
+        MENU_ITEM_FUNCTION_P(_i("Dump to serial"), lcd_serial_dump);
+#endif
 #ifdef DEBUG_BUILD
+#ifdef EMERGENCY_HANDLERS
+#ifdef WATCHDOG
+    MENU_ITEM_FUNCTION_P(PSTR("WDR crash"), lcd_wdr_crash);
+#endif //WATCHDOG
+    MENU_ITEM_FUNCTION_P(PSTR("Stack crash"), lcd_stack_crash);
+#endif //EMERGENCY_HANDLERS
   MENU_ITEM_SUBMENU_P(PSTR("Debug"), lcd_menu_debug);////MSG_DEBUG c=18
 #endif /* DEBUG_BUILD */
 
@@ -6695,12 +6761,6 @@ static void lcd_main_menu()
 
 }
 
-void stack_error() {
-	Sound_MakeCustom(1000,0,true);
-	lcd_display_message_fullscreen_P(_i("Error - static memory has been overwritten"));////MSG_STACK_ERROR c=20 r=4
-	//err_triggered = 1;
-	 while (1) delay_keep_alive(1000);
-}
 
 #ifdef DEBUG_STEPPER_TIMER_MISSED
 bool stepper_timer_overflow_state = false;
@@ -8928,7 +8988,6 @@ void menu_lcd_lcdupdate_func(void)
 		if (lcd_draw_update) lcd_draw_update--;
 		lcd_next_update_millis = _millis() + LCD_UPDATE_INTERVAL;
 	}
-	if (!SdFatUtil::test_stack_integrity()) stack_error();
 	lcd_ping(); //check that we have received ping command if we are in farm mode
 	lcd_send_status();
 	if (lcd_commands_type == LcdCommands::Layer1Cal) lcd_commands();

+ 0 - 7
Firmware/ultralcd.h

@@ -1,15 +1,9 @@
 #ifndef ULTRALCD_H
 #define ULTRALCD_H
 
-#include "Marlin.h"
-#include "lcd.h"
-#include "conv2str.h"
-#include "menu.h"
 #include "mesh_bed_calibration.h"
 #include "config.h"
 
-#include "config.h"
-
 extern void menu_lcd_longpress_func(void);
 extern void menu_lcd_charsetup_func(void);
 extern void menu_lcd_lcdupdate_func(void);
@@ -200,7 +194,6 @@ void mFilamentItemForce();
 void lcd_generic_preheat_menu();
 void unload_filament(bool automatic = false);
 
-void stack_error();
 void lcd_printer_connected();
 void lcd_ping();
 

+ 1 - 0
Firmware/util.cpp

@@ -1,6 +1,7 @@
 #include "Configuration.h"
 
 #include "ultralcd.h"
+#include "menu.h"
 #include "sound.h"
 #include "language.h"
 #include "util.h"

+ 1 - 0
Firmware/variants/1_75mm_MK2-RAMBo10a-E3Dv6full.h

@@ -418,6 +418,7 @@ THERMISTORS SETTINGS
 #endif
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 1 - 0
Firmware/variants/1_75mm_MK2-RAMBo13a-E3Dv6full.h

@@ -407,6 +407,7 @@ THERMISTORS SETTINGS
 #endif
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 6 - 1
Firmware/variants/1_75mm_MK25-RAMBo10a-E3Dv6full.h

@@ -119,11 +119,15 @@
 #define DEFAULT_SAFETYTIMER_TIME_MINS 30
 #define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
 
+// Online crash dumper
+#define EMERGENCY_SERIAL_DUMP   // Request dump via serial on stack corruption and WDR
+#define MENU_SERIAL_DUMP        // Enable "Memory dump" in Settings menu
+
 // Filament sensor
 #define FILAMENT_SENSOR
 #define PAT9125
 
-
+#define DEBUG_DCODE2
 #define DEBUG_DCODE3
 
 //#define DEBUG_BUILD
@@ -469,6 +473,7 @@
 #define TEMP_SENSOR_PINDA 1
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 6 - 1
Firmware/variants/1_75mm_MK25-RAMBo13a-E3Dv6full.h

@@ -120,11 +120,15 @@
 #define DEFAULT_SAFETYTIMER_TIME_MINS 30
 #define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
 
+// Online crash dumper
+#define EMERGENCY_SERIAL_DUMP   // Request dump via serial on stack corruption and WDR
+#define MENU_SERIAL_DUMP        // Enable "Memory dump" in Settings menu
+
 // Filament sensor
 #define FILAMENT_SENSOR
 #define PAT9125
 
-
+#define DEBUG_DCODE2
 #define DEBUG_DCODE3
 
 //#define DEBUG_BUILD
@@ -470,6 +474,7 @@
 #define TEMP_SENSOR_PINDA 1
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 6 - 1
Firmware/variants/1_75mm_MK25S-RAMBo10a-E3Dv6full.h

@@ -119,11 +119,15 @@
 #define DEFAULT_SAFETYTIMER_TIME_MINS 30
 #define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
 
+// Online crash dumper
+#define EMERGENCY_SERIAL_DUMP   // Request dump via serial on stack corruption and WDR
+#define MENU_SERIAL_DUMP        // Enable "Memory dump" in Settings menu
+
 // Filament sensor
 #define FILAMENT_SENSOR
 #define IR_SENSOR
 
-
+#define DEBUG_DCODE2
 #define DEBUG_DCODE3
 
 //#define DEBUG_BUILD
@@ -469,6 +473,7 @@
 #define TEMP_SENSOR_PINDA 1
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 6 - 1
Firmware/variants/1_75mm_MK25S-RAMBo13a-E3Dv6full.h

@@ -120,11 +120,15 @@
 #define DEFAULT_SAFETYTIMER_TIME_MINS 30
 #define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
 
+// Online crash dumper
+#define EMERGENCY_SERIAL_DUMP   // Request dump via serial on stack corruption and WDR
+#define MENU_SERIAL_DUMP        // Enable "Memory dump" in Settings menu
+
 // Filament sensor
 #define FILAMENT_SENSOR
 #define IR_SENSOR
 
-
+#define DEBUG_DCODE2
 #define DEBUG_DCODE3
 
 //#define DEBUG_BUILD
@@ -470,6 +474,7 @@
 #define TEMP_SENSOR_PINDA 1
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 12 - 0
Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h

@@ -139,6 +139,15 @@
 #define DEFAULT_SAFETYTIMER_TIME_MINS 30
 #define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
 
+// Offline crash dumper
+#define XFLASH_DUMP     // enable dump functionality (including D20/D21/D22)
+#define MENU_DUMP       // enable "Memory dump" in Settings menu
+#define EMERGENCY_DUMP  // trigger crash on stack corruption and WDR
+
+// Online crash dumper
+//#define EMERGENCY_SERIAL_DUMP   // Request dump via serial on stack corruption and WDR
+//#define MENU_SERIAL_DUMP        // Enable "Memory dump" in Settings menu
+
 // Filament sensor
 #define FILAMENT_SENSOR
 #define PAT9125
@@ -155,7 +164,9 @@
 #define MINTEMP_MINAMBIENT      10
 #define MINTEMP_MINAMBIENT_RAW  1002
 
+#define DEBUG_DCODE2
 #define DEBUG_DCODE3
+#define DEBUG_DCODE6
 
 //#define DEBUG_BUILD
 //#define DEBUG_SEC_LANG   //secondary language debug output at startup
@@ -591,6 +602,7 @@
 #define TEMP_SENSOR_AMBIENT 2000
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 12 - 0
Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h

@@ -141,6 +141,15 @@
 #define DEFAULT_SAFETYTIMER_TIME_MINS 30
 #define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
 
+// Offline crash dumper
+#define XFLASH_DUMP     // enable dump functionality (including D20/D21/D22)
+#define MENU_DUMP       // enable "Memory dump" in Settings menu
+#define EMERGENCY_DUMP  // trigger crash on stack corruption and WDR
+
+// Online crash dumper
+//#define EMERGENCY_SERIAL_DUMP   // Request dump via serial on stack corruption and WDR
+//#define MENU_SERIAL_DUMP        // Enable "Memory dump" in Settings menu
+
 // Filament sensor
 #define FILAMENT_SENSOR
 #define IR_SENSOR
@@ -157,7 +166,9 @@
 #define MINTEMP_MINAMBIENT      10
 #define MINTEMP_MINAMBIENT_RAW  1002
 
+#define DEBUG_DCODE2
 #define DEBUG_DCODE3
+#define DEBUG_DCODE6
 
 //#define DEBUG_BUILD
 //#define DEBUG_SEC_LANG   //secondary language debug output at startup
@@ -595,6 +606,7 @@
 #define TEMP_SENSOR_AMBIENT 2000
 
 #define STACK_GUARD_TEST_VALUE 0xA2A2
+#define STACK_GUARD_MARGIN     32
 
 #define MAX_BED_TEMP_CALIBRATION 50
 #define MAX_HOTEND_TEMP_CALIBRATION 50

+ 33 - 15
Firmware/xflash.c

@@ -90,13 +90,18 @@ void w25x20cl_wr_status_reg(uint8_t val)
 }
 #endif
 
-void xflash_rd_data(uint32_t addr, uint8_t* data, uint16_t cnt)
+static void xflash_send_cmdaddr(uint8_t cmd, uint32_t addr)
 {
-	_CS_LOW();
-	_SPI_TX(_CMD_RD_DATA);               // send command 0x03
+	_SPI_TX(cmd);                        // send command 0x03
 	_SPI_TX(((uint8_t*)&addr)[2]);       // send addr bits 16..23
 	_SPI_TX(((uint8_t*)&addr)[1]);       // send addr bits 8..15
 	_SPI_TX(((uint8_t*)&addr)[0]);       // send addr bits 0..7
+}
+
+void xflash_rd_data(uint32_t addr, uint8_t* data, uint16_t cnt)
+{
+	_CS_LOW();
+	xflash_send_cmdaddr(_CMD_RD_DATA, addr);
 	while (cnt--)                        // receive data
 		*(data++) = _SPI_RX();
 	_CS_HIGH();
@@ -105,22 +110,38 @@ void xflash_rd_data(uint32_t addr, uint8_t* data, uint16_t cnt)
 void xflash_page_program(uint32_t addr, uint8_t* data, uint16_t cnt)
 {
 	_CS_LOW();
-	_SPI_TX(_CMD_PAGE_PROGRAM);          // send command 0x02
-	_SPI_TX(((uint8_t*)&addr)[2]);       // send addr bits 16..23
-	_SPI_TX(((uint8_t*)&addr)[1]);       // send addr bits 8..15
-	_SPI_TX(((uint8_t*)&addr)[0]);       // send addr bits 0..7
+	xflash_send_cmdaddr(_CMD_PAGE_PROGRAM, addr);
 	while (cnt--)                        // send data
 		_SPI_TX(*(data++));
 	_CS_HIGH();
 }
 
+void xflash_multipage_program(uint32_t addr, uint8_t* data, uint16_t cnt)
+{
+    while(cnt)
+    {
+        xflash_enable_wr();
+        _CS_LOW();
+        xflash_send_cmdaddr(_CMD_PAGE_PROGRAM, addr);
+        while(1)
+        {
+            // send data
+            _SPI_TX(*(data++));
+            if(!--cnt || !(++addr & 0xFF))
+            {
+                // on a page boundary or end of write
+                _CS_HIGH();
+                xflash_wait_busy();
+                break;
+            }
+        }
+    }
+}
+
 void xflash_page_program_P(uint32_t addr, uint8_t* data, uint16_t cnt)
 {
 	_CS_LOW();
-	_SPI_TX(_CMD_PAGE_PROGRAM);          // send command 0x02
-	_SPI_TX(((uint8_t*)&addr)[2]);       // send addr bits 16..23
-	_SPI_TX(((uint8_t*)&addr)[1]);       // send addr bits 8..15
-	_SPI_TX(((uint8_t*)&addr)[0]);       // send addr bits 0..7
+    xflash_send_cmdaddr(_CMD_PAGE_PROGRAM, addr);
 	while (cnt--)                        // send data
 		_SPI_TX(pgm_read_byte(data++));
 	_CS_HIGH();
@@ -129,10 +150,7 @@ void xflash_page_program_P(uint32_t addr, uint8_t* data, uint16_t cnt)
 void xflash_erase(uint8_t cmd, uint32_t addr)
 {
 	_CS_LOW();
-	_SPI_TX(cmd);          			     // send command 0x20
-	_SPI_TX(((uint8_t*)&addr)[2]);       // send addr bits 16..23
-	_SPI_TX(((uint8_t*)&addr)[1]);       // send addr bits 8..15
-	_SPI_TX(((uint8_t*)&addr)[0]);       // send addr bits 0..7
+    xflash_send_cmdaddr(cmd, addr);
 	_CS_HIGH();
 }
 

+ 12 - 3
Firmware/xflash.h

@@ -34,16 +34,25 @@ extern uint8_t xflash_rd_status_reg(void);
 extern void w25x20cl_wr_status_reg(uint8_t val);
 #endif
 extern void xflash_rd_data(uint32_t addr, uint8_t* data, uint16_t cnt);
-extern void xflash_page_program(uint32_t addr, uint8_t* data, uint16_t cnt);
-extern void xflash_page_program_P(uint32_t addr, uint8_t* data, uint16_t cnt);
+
 extern void xflash_sector_erase(uint32_t addr);
 extern void xflash_block32_erase(uint32_t addr);
 extern void xflash_block64_erase(uint32_t addr);
 extern void xflash_chip_erase(void);
-extern void xflash_page_program(uint32_t addr, uint8_t* data, uint16_t cnt);
 extern void xflash_rd_uid(uint8_t* uid);
 extern void xflash_wait_busy(void);
 
+// write up to a single page of data (256bytes)
+extern void xflash_page_program(uint32_t addr, uint8_t* data, uint16_t cnt);
+
+// write up to a single page of data from program memory
+extern void xflash_page_program_P(uint32_t addr, uint8_t* data, uint16_t cnt);
+
+// xflash_multipage_program: high-level interface for multi-page writes.
+//   Write any amount of data, chunking writes to page boundaries as needed.
+//   Automatically enables writes and waits for completion.
+extern void xflash_multipage_program(uint32_t addr, uint8_t* data, uint16_t cnt);
+
 #if defined(__cplusplus)
 }
 #endif //defined(__cplusplus)

+ 109 - 0
Firmware/xflash_dump.cpp

@@ -0,0 +1,109 @@
+#include <stddef.h>
+
+#include <avr/wdt.h>
+#include <avr/interrupt.h>
+
+#include "xflash_dump.h"
+#ifdef XFLASH_DUMP
+#include "asm.h"
+#include "xflash.h"
+#include "Marlin.h" // for softReset
+
+bool xfdump_check_state(dump_crash_reason* reason)
+{
+    uint32_t magic;
+
+    XFLASH_SPI_ENTER();
+    xflash_rd_data(DUMP_OFFSET + offsetof(dump_t, header.magic),
+                   (uint8_t*)&magic, sizeof(magic));
+    if (magic != DUMP_MAGIC)
+        return false;
+
+    if (reason)
+    {
+        xflash_rd_data(DUMP_OFFSET + offsetof(dump_t, header.crash_reason),
+                       (uint8_t*)reason, sizeof(*reason));
+    }
+    return true;
+}
+
+
+void xfdump_reset()
+{
+    XFLASH_SPI_ENTER();
+    xflash_enable_wr();
+    xflash_sector_erase(DUMP_OFFSET + offsetof(dump_t, header.magic));
+    xflash_wait_busy();
+}
+
+
+static void xfdump_erase()
+{
+    for(uint32_t addr = DUMP_OFFSET;
+        addr < DUMP_OFFSET + DUMP_SIZE;
+        addr += 4096)
+    {
+        xflash_enable_wr();
+        xflash_sector_erase(DUMP_OFFSET);
+        xflash_wait_busy();
+    }
+}
+
+
+static void __attribute__((noinline)) xfdump_dump_core(dump_header_t& hdr, uint32_t addr, uint8_t* buf, uint16_t cnt)
+{
+    XFLASH_SPI_ENTER();
+
+    // start by clearing all sectors (we need all of them in any case)
+    xfdump_erase();
+
+    // sample SP/PC
+    hdr.sp = SP;
+    GETPC(&hdr.pc);
+
+    // write header
+    static_assert(sizeof(hdr) <= 256, "header is larger than a single page write");
+    xflash_enable_wr();
+    xflash_page_program(DUMP_OFFSET, (uint8_t*)&hdr, sizeof(hdr));
+    xflash_wait_busy();
+
+    // write data
+    static_assert(sizeof(dump_t::data) <= RAMEND+1, "dump area size insufficient");
+    xflash_multipage_program(addr, buf, cnt);
+}
+
+
+void xfdump_dump()
+{
+    dump_header_t buf;
+    buf.magic = DUMP_MAGIC;
+    buf.regs_present = false;
+    buf.crash_reason = (uint8_t)dump_crash_reason::manual;
+
+    // write sram only
+    xfdump_dump_core(buf, DUMP_OFFSET + offsetof(dump_t, data.sram),
+                     (uint8_t*)RAMSTART, RAMSIZE);
+}
+
+
+void xfdump_full_dump_and_reset(dump_crash_reason reason)
+{
+    dump_header_t buf;
+    buf.magic = DUMP_MAGIC;
+    buf.regs_present = true;
+    buf.crash_reason = (uint8_t)reason;
+
+    // disable interrupts for a cleaner register dump
+    cli();
+
+    // ensure there's always enough time (with some margin) to dump
+    // dump time on w25x20cl: ~150ms
+    wdt_enable(WDTO_500MS);
+
+    // write all addressable ranges (this will trash bidirectional registers)
+    xfdump_dump_core(buf, DUMP_OFFSET + offsetof(dump_t, data), 0, RAMEND+1);
+
+    // force a reset even sooner
+    softReset();
+}
+#endif

+ 22 - 0
Firmware/xflash_dump.h

@@ -0,0 +1,22 @@
+// XFLASH dumper
+#pragma once
+#include "xflash_layout.h"
+
+enum class dump_crash_reason : uint8_t
+{
+    manual = 0,
+    stack_error,
+    watchdog,
+    bad_isr,
+};
+
+#ifdef XFLASH_DUMP
+void xfdump_reset();    // reset XFLASH dump state
+void xfdump_dump();     // create a new SRAM memory dump
+
+// return true if a dump is present, save type in "reason" if provided
+bool xfdump_check_state(dump_crash_reason* reason = NULL);
+
+// create a new dump containing registers and SRAM, then reset
+void xfdump_full_dump_and_reset(dump_crash_reason crash = dump_crash_reason::manual);
+#endif

+ 51 - 0
Firmware/xflash_layout.h

@@ -0,0 +1,51 @@
+// XFLASH memory layout
+#pragma once
+#include <stdint.h>
+#include "bootapp.h" // for RAMSIZE
+#include "config.h"
+
+#define XFLASH_SIZE 0x40000ul // size of XFLASH
+#define LANG_OFFSET 0x0       // offset for language data
+
+#ifndef XFLASH_DUMP
+#define LANG_SIZE   XFLASH_SIZE
+#else
+
+#define DUMP_MAGIC  0x55525547ul
+
+struct dump_header_t
+{
+    // start with a magic value to indicate the presence of a dump, so that clearing
+    // a single page is sufficient for resetting the state
+    uint32_t magic;
+
+    uint8_t regs_present; // true when the lower segment containing registers is present
+    uint8_t crash_reason; // uses values from dump_crash_source
+
+    uint32_t pc;          // PC nearby the crash location
+    uint16_t sp;          // SP nearby the crash location
+};
+
+struct dump_data_t
+{
+    // contiguous region containing all addressable ranges
+    uint8_t regs[RAMSTART];
+    uint8_t sram[RAMSIZE];
+};
+
+struct dump_t
+{
+    struct dump_header_t header;
+
+    // data is page aligned (no real space waste, due to the larger
+    // alignment required for the whole dump)
+    struct dump_data_t __attribute__((aligned(256))) data;
+};
+
+// dump offset must be aligned to lower 4kb sector boundary
+#define DUMP_OFFSET ((XFLASH_SIZE - sizeof(dump_t)) & ~0xFFFul)
+
+#define DUMP_SIZE   (XFLASH_SIZE - DUMP_OFFSET) // effective dump size area
+#define LANG_SIZE   DUMP_OFFSET                 // available language space
+
+#endif

+ 3 - 0
Firmware/xyzcal.cpp

@@ -163,6 +163,9 @@ void xyzcal_meassure_leave(void)
 	ENABLE_STEPPER_DRIVER_INTERRUPT();
 #ifdef WATCHDOG
 	wdt_enable(WDTO_4S);
+#ifdef EMERGENCY_HANDLERS
+	WDTCSR |= (1 << WDIE);
+#endif //EMERGENCY_HANDLERS
 #endif //WATCHDOG
 	sm4_stop_cb = 0;
 	sm4_update_pos_cb = 0;

+ 15 - 0
lang/fw-build.sh

@@ -198,6 +198,21 @@ if [ -e lang_nl.bin ]; then cat lang_nl.bin >> lang.bin; fi
 ## New language
 #if [ -e lang_qr.bin ]; then cat lang_qr.bin >> lang.bin; fi
 
+# Check that the language data doesn't exceed the reserved XFLASH space
+echo " checking language data size:"
+lang_size=$(wc -c lang.bin | cut -f1 -d' ')
+lang_size_pad=$(( ($lang_size+4096-1) / 4096 * 4096 ))
+
+# TODO: hard-coded! get value by preprocessing LANG_SIZE from xflash_layout.h!
+lang_reserved=249856
+
+echo "  total size usage: $lang_size_pad ($lang_size)"
+echo "  reserved size:    $lang_reserved"
+if [ $lang_size_pad -gt $lang_reserved ]; then
+  echo "NG! - language data too large" >&2
+  finish 1
+fi
+
 #convert lang.bin to lang.hex
 echo -n " converting to hex..." >&2
 $OBJCOPY -I binary -O ihex ./lang.bin ./lang.hex