✨ D576 Buffer Monitoring (#19674)
This commit is contained in:
parent
416234f43a
commit
51d954a4fd
|
@ -4177,6 +4177,14 @@
|
||||||
// Enable Marlin dev mode which adds some special commands
|
// Enable Marlin dev mode which adds some special commands
|
||||||
//#define MARLIN_DEV_MODE
|
//#define MARLIN_DEV_MODE
|
||||||
|
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
/**
|
||||||
|
* D576 - Buffer Monitoring
|
||||||
|
* To help diagnose print quality issues stemming from empty command buffers.
|
||||||
|
*/
|
||||||
|
//#define BUFFER_MONITORING
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Postmortem Debugging captures misbehavior and outputs the CPU status and backtrace to serial.
|
* Postmortem Debugging captures misbehavior and outputs the CPU status and backtrace to serial.
|
||||||
* When running in the debugger it will break for debugging. This is useful to help understand
|
* When running in the debugger it will break for debugging. This is useful to help understand
|
||||||
|
|
|
@ -868,6 +868,7 @@ void idle(bool no_stepper_sleep/*=false*/) {
|
||||||
TERN_(AUTO_REPORT_TEMPERATURES, thermalManager.auto_reporter.tick());
|
TERN_(AUTO_REPORT_TEMPERATURES, thermalManager.auto_reporter.tick());
|
||||||
TERN_(AUTO_REPORT_SD_STATUS, card.auto_reporter.tick());
|
TERN_(AUTO_REPORT_SD_STATUS, card.auto_reporter.tick());
|
||||||
TERN_(AUTO_REPORT_POSITION, position_auto_reporter.tick());
|
TERN_(AUTO_REPORT_POSITION, position_auto_reporter.tick());
|
||||||
|
TERN_(BUFFER_MONITORING, queue.auto_report_buffer_statistics());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,7 @@
|
||||||
* M553 - Get or set IP netmask. (Requires enabled Ethernet port)
|
* M553 - Get or set IP netmask. (Requires enabled Ethernet port)
|
||||||
* M554 - Get or set IP gateway. (Requires enabled Ethernet port)
|
* M554 - Get or set IP gateway. (Requires enabled Ethernet port)
|
||||||
* M569 - Enable stealthChop on an axis. (Requires at least one _DRIVER_TYPE to be TMC2130/2160/2208/2209/5130/5160)
|
* M569 - Enable stealthChop on an axis. (Requires at least one _DRIVER_TYPE to be TMC2130/2160/2208/2209/5130/5160)
|
||||||
|
* M575 - Change the serial baud rate. (Requires BAUD_RATE_GCODE)
|
||||||
* M600 - Pause for filament change: "M600 X<pos> Y<pos> Z<raise> E<first_retract> L<later_retract>". (Requires ADVANCED_PAUSE_FEATURE)
|
* M600 - Pause for filament change: "M600 X<pos> Y<pos> Z<raise> E<first_retract> L<later_retract>". (Requires ADVANCED_PAUSE_FEATURE)
|
||||||
* M603 - Configure filament change: "M603 T<tool> U<unload_length> L<load_length>". (Requires ADVANCED_PAUSE_FEATURE)
|
* M603 - Configure filament change: "M603 T<tool> U<unload_length> L<load_length>". (Requires ADVANCED_PAUSE_FEATURE)
|
||||||
* M605 - Set Dual X-Carriage movement mode: "M605 S<mode> [X<x_offset>] [R<temp_offset>]". (Requires DUAL_X_CARRIAGE)
|
* M605 - Set Dual X-Carriage movement mode: "M605 S<mode> [X<x_offset>] [R<temp_offset>]". (Requires DUAL_X_CARRIAGE)
|
||||||
|
@ -298,6 +299,7 @@
|
||||||
* M997 - Perform in-application firmware update
|
* M997 - Perform in-application firmware update
|
||||||
* M999 - Restart after being stopped by error
|
* M999 - Restart after being stopped by error
|
||||||
* D... - Custom Development G-code. Add hooks to 'gcode_D.cpp' for developers to test features. (Requires MARLIN_DEV_MODE)
|
* D... - Custom Development G-code. Add hooks to 'gcode_D.cpp' for developers to test features. (Requires MARLIN_DEV_MODE)
|
||||||
|
* D576 - Set buffer monitoring options. (Requires BUFFER_MONITORING)
|
||||||
*
|
*
|
||||||
* "T" Codes
|
* "T" Codes
|
||||||
*
|
*
|
||||||
|
|
|
@ -23,248 +23,279 @@
|
||||||
|
|
||||||
#if ENABLED(MARLIN_DEV_MODE)
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
|
||||||
#include "gcode.h"
|
#include "gcode.h"
|
||||||
#include "../module/settings.h"
|
|
||||||
#include "../module/temperature.h"
|
|
||||||
#include "../libs/hex_print.h"
|
|
||||||
#include "../HAL/shared/eeprom_if.h"
|
|
||||||
#include "../HAL/shared/Delay.h"
|
|
||||||
#include "../sd/cardreader.h"
|
|
||||||
#include "../MarlinCore.h" // for kill
|
|
||||||
|
|
||||||
extern void dump_delay_accuracy_check();
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
#include "queue.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
#include "../module/settings.h"
|
||||||
* Dn: G-code for development and testing
|
#include "../module/temperature.h"
|
||||||
*
|
#include "../libs/hex_print.h"
|
||||||
* See https://reprap.org/wiki/G-code#D:_Debug_codes
|
#include "../HAL/shared/eeprom_if.h"
|
||||||
*
|
#include "../HAL/shared/Delay.h"
|
||||||
* Put whatever else you need here to test ongoing development.
|
#include "../sd/cardreader.h"
|
||||||
*/
|
#include "../MarlinCore.h" // for kill
|
||||||
void GcodeSuite::D(const int16_t dcode) {
|
|
||||||
switch (dcode) {
|
|
||||||
|
|
||||||
case -1:
|
extern void dump_delay_accuracy_check();
|
||||||
for (;;) { /* loop forever (watchdog reset) */ }
|
|
||||||
|
|
||||||
case 0:
|
/**
|
||||||
HAL_reboot();
|
* Dn: G-code for development and testing
|
||||||
break;
|
*
|
||||||
|
* See https://reprap.org/wiki/G-code#D:_Debug_codes
|
||||||
|
*
|
||||||
|
* Put whatever else you need here to test ongoing development.
|
||||||
|
*/
|
||||||
|
void GcodeSuite::D(const int16_t dcode) {
|
||||||
|
switch (dcode) {
|
||||||
|
|
||||||
case 10:
|
case -1:
|
||||||
kill(PSTR("D10"), PSTR("KILL TEST"), parser.seen_test('P'));
|
for (;;) { /* loop forever (watchdog reset) */ }
|
||||||
break;
|
|
||||||
|
|
||||||
case 1: {
|
case 0:
|
||||||
// Zero or pattern-fill the EEPROM data
|
HAL_reboot();
|
||||||
#if ENABLED(EEPROM_SETTINGS)
|
break;
|
||||||
persistentStore.access_start();
|
|
||||||
size_t total = persistentStore.capacity();
|
|
||||||
int pos = 0;
|
|
||||||
const uint8_t value = 0x0;
|
|
||||||
while (total--) persistentStore.write_data(pos, &value, 1);
|
|
||||||
persistentStore.access_finish();
|
|
||||||
#else
|
|
||||||
settings.reset();
|
|
||||||
settings.save();
|
|
||||||
#endif
|
|
||||||
HAL_reboot();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case 2: { // D2 Read / Write SRAM
|
case 10:
|
||||||
#define SRAM_SIZE 8192
|
kill(PSTR("D10"), PSTR("KILL TEST"), parser.seen_test('P'));
|
||||||
uint8_t *pointer = parser.hex_adr_val('A');
|
break;
|
||||||
uint16_t len = parser.ushortval('C', 1);
|
|
||||||
uintptr_t addr = (uintptr_t)pointer;
|
|
||||||
NOMORE(addr, size_t(SRAM_SIZE - 1));
|
|
||||||
NOMORE(len, SRAM_SIZE - addr);
|
|
||||||
if (parser.seenval('X')) {
|
|
||||||
// Write the hex bytes after the X
|
|
||||||
uint16_t val = parser.hex_val('X');
|
|
||||||
while (len--) {
|
|
||||||
*pointer = val;
|
|
||||||
pointer++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
while (len--) print_hex_byte(*(pointer++));
|
|
||||||
SERIAL_EOL();
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
// Zero or pattern-fill the EEPROM data
|
||||||
#if ENABLED(EEPROM_SETTINGS)
|
#if ENABLED(EEPROM_SETTINGS)
|
||||||
case 3: { // D3 Read / Write EEPROM
|
persistentStore.access_start();
|
||||||
uint8_t *pointer = parser.hex_adr_val('A');
|
size_t total = persistentStore.capacity();
|
||||||
uint16_t len = parser.ushortval('C', 1);
|
int pos = 0;
|
||||||
uintptr_t addr = (uintptr_t)pointer;
|
const uint8_t value = 0x0;
|
||||||
NOMORE(addr, size_t(persistentStore.capacity() - 1));
|
while (total--) persistentStore.write_data(pos, &value, 1);
|
||||||
NOMORE(len, persistentStore.capacity() - addr);
|
persistentStore.access_finish();
|
||||||
if (parser.seenval('X')) {
|
#else
|
||||||
uint16_t val = parser.hex_val('X');
|
settings.reset();
|
||||||
#if ENABLED(EEPROM_SETTINGS)
|
settings.save();
|
||||||
persistentStore.access_start();
|
|
||||||
while (len--) {
|
|
||||||
int pos = 0;
|
|
||||||
persistentStore.write_data(pos, (uint8_t *)&val, sizeof(val));
|
|
||||||
}
|
|
||||||
SERIAL_EOL();
|
|
||||||
persistentStore.access_finish();
|
|
||||||
#else
|
|
||||||
SERIAL_ECHOLNPGM("NO EEPROM");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Read bytes from EEPROM
|
|
||||||
#if ENABLED(EEPROM_SETTINGS)
|
|
||||||
persistentStore.access_start();
|
|
||||||
int pos = 0;
|
|
||||||
uint8_t val;
|
|
||||||
while (len--) if (!persistentStore.read_data(pos, &val, 1)) print_hex_byte(val);
|
|
||||||
SERIAL_EOL();
|
|
||||||
persistentStore.access_finish();
|
|
||||||
#else
|
|
||||||
SERIAL_ECHOLNPGM("NO EEPROM");
|
|
||||||
len = 0;
|
|
||||||
#endif
|
|
||||||
SERIAL_EOL();
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
#endif
|
#endif
|
||||||
|
HAL_reboot();
|
||||||
|
} break;
|
||||||
|
|
||||||
case 4: { // D4 Read / Write PIN
|
case 2: { // D2 Read / Write SRAM
|
||||||
//const bool is_out = parser.boolval('F');
|
#define SRAM_SIZE 8192
|
||||||
//const uint8_t pin = parser.byteval('P'),
|
uint8_t *pointer = parser.hex_adr_val('A');
|
||||||
// val = parser.byteval('V', LOW);
|
uint16_t len = parser.ushortval('C', 1);
|
||||||
if (parser.seenval('X')) {
|
uintptr_t addr = (uintptr_t)pointer;
|
||||||
// TODO: Write the hex bytes after the X
|
NOMORE(addr, size_t(SRAM_SIZE - 1));
|
||||||
//while (len--) {
|
NOMORE(len, SRAM_SIZE - addr);
|
||||||
//}
|
if (parser.seenval('X')) {
|
||||||
|
// Write the hex bytes after the X
|
||||||
|
uint16_t val = parser.hex_val('X');
|
||||||
|
while (len--) {
|
||||||
|
*pointer = val;
|
||||||
|
pointer++;
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
//while (len--) {
|
else {
|
||||||
//// TODO: Read bytes from EEPROM
|
while (len--) print_hex_byte(*(pointer++));
|
||||||
// print_hex_byte(eeprom_read_byte(adr++));
|
SERIAL_EOL();
|
||||||
//}
|
}
|
||||||
SERIAL_EOL();
|
} break;
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case 5: { // D5 Read / Write onboard Flash
|
#if ENABLED(EEPROM_SETTINGS)
|
||||||
#define FLASH_SIZE 1024
|
case 3: { // D3 Read / Write EEPROM
|
||||||
uint8_t *pointer = parser.hex_adr_val('A');
|
uint8_t *pointer = parser.hex_adr_val('A');
|
||||||
uint16_t len = parser.ushortval('C', 1);
|
uint16_t len = parser.ushortval('C', 1);
|
||||||
uintptr_t addr = (uintptr_t)pointer;
|
uintptr_t addr = (uintptr_t)pointer;
|
||||||
NOMORE(addr, size_t(FLASH_SIZE - 1));
|
NOMORE(addr, size_t(persistentStore.capacity() - 1));
|
||||||
NOMORE(len, FLASH_SIZE - addr);
|
NOMORE(len, persistentStore.capacity() - addr);
|
||||||
if (parser.seenval('X')) {
|
if (parser.seenval('X')) {
|
||||||
// TODO: Write the hex bytes after the X
|
uint16_t val = parser.hex_val('X');
|
||||||
//while (len--) {}
|
#if ENABLED(EEPROM_SETTINGS)
|
||||||
|
persistentStore.access_start();
|
||||||
|
while (len--) {
|
||||||
|
int pos = 0;
|
||||||
|
persistentStore.write_data(pos, (uint8_t *)&val, sizeof(val));
|
||||||
|
}
|
||||||
|
SERIAL_EOL();
|
||||||
|
persistentStore.access_finish();
|
||||||
|
#else
|
||||||
|
SERIAL_ECHOLNPGM("NO EEPROM");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//while (len--) {
|
// Read bytes from EEPROM
|
||||||
//// TODO: Read bytes from EEPROM
|
#if ENABLED(EEPROM_SETTINGS)
|
||||||
// print_hex_byte(eeprom_read_byte(adr++));
|
persistentStore.access_start();
|
||||||
//}
|
int pos = 0;
|
||||||
|
uint8_t val;
|
||||||
|
while (len--) if (!persistentStore.read_data(pos, &val, 1)) print_hex_byte(val);
|
||||||
|
SERIAL_EOL();
|
||||||
|
persistentStore.access_finish();
|
||||||
|
#else
|
||||||
|
SERIAL_ECHOLNPGM("NO EEPROM");
|
||||||
|
len = 0;
|
||||||
|
#endif
|
||||||
SERIAL_EOL();
|
SERIAL_EOL();
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
#endif
|
||||||
|
|
||||||
case 6: // D6 Check delay loop accuracy
|
case 4: { // D4 Read / Write PIN
|
||||||
dump_delay_accuracy_check();
|
//const bool is_out = parser.boolval('F');
|
||||||
break;
|
//const uint8_t pin = parser.byteval('P'),
|
||||||
|
// val = parser.byteval('V', LOW);
|
||||||
|
if (parser.seenval('X')) {
|
||||||
|
// TODO: Write the hex bytes after the X
|
||||||
|
//while (len--) {
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//while (len--) {
|
||||||
|
//// TODO: Read bytes from EEPROM
|
||||||
|
// print_hex_byte(eeprom_read_byte(adr++));
|
||||||
|
//}
|
||||||
|
SERIAL_EOL();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
case 7: // D7 dump the current serial port type (hence configuration)
|
case 5: { // D5 Read / Write onboard Flash
|
||||||
SERIAL_ECHOLNPAIR("Current serial configuration RX_BS:", RX_BUFFER_SIZE, ", TX_BS:", TX_BUFFER_SIZE);
|
#define FLASH_SIZE 1024
|
||||||
SERIAL_ECHOLN(gtn(&SERIAL_IMPL));
|
uint8_t *pointer = parser.hex_adr_val('A');
|
||||||
break;
|
uint16_t len = parser.ushortval('C', 1);
|
||||||
|
uintptr_t addr = (uintptr_t)pointer;
|
||||||
|
NOMORE(addr, size_t(FLASH_SIZE - 1));
|
||||||
|
NOMORE(len, FLASH_SIZE - addr);
|
||||||
|
if (parser.seenval('X')) {
|
||||||
|
// TODO: Write the hex bytes after the X
|
||||||
|
//while (len--) {}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//while (len--) {
|
||||||
|
//// TODO: Read bytes from EEPROM
|
||||||
|
// print_hex_byte(eeprom_read_byte(adr++));
|
||||||
|
//}
|
||||||
|
SERIAL_EOL();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test)
|
case 6: // D6 Check delay loop accuracy
|
||||||
SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog");
|
dump_delay_accuracy_check();
|
||||||
SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")");
|
break;
|
||||||
thermalManager.disable_all_heaters();
|
|
||||||
delay(1000); // Allow time to print
|
case 7: // D7 dump the current serial port type (hence configuration)
|
||||||
DISABLE_ISRS();
|
SERIAL_ECHOLNPAIR("Current serial configuration RX_BS:", RX_BUFFER_SIZE, ", TX_BS:", TX_BUFFER_SIZE);
|
||||||
// Use a low-level delay that does not rely on interrupts to function
|
SERIAL_ECHOLN(gtn(&SERIAL_IMPL));
|
||||||
// Do not spin forever, to avoid thermal risks if heaters are enabled and
|
break;
|
||||||
// watchdog does not work.
|
|
||||||
for (int i = 10000; i--;) DELAY_US(1000UL);
|
case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test)
|
||||||
ENABLE_ISRS();
|
SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog");
|
||||||
SERIAL_ECHOLNPGM("FAILURE: Watchdog did not trigger board reset.");
|
SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")");
|
||||||
|
thermalManager.disable_all_heaters();
|
||||||
|
delay(1000); // Allow time to print
|
||||||
|
DISABLE_ISRS();
|
||||||
|
// Use a low-level delay that does not rely on interrupts to function
|
||||||
|
// Do not spin forever, to avoid thermal risks if heaters are enabled and
|
||||||
|
// watchdog does not work.
|
||||||
|
for (int i = 10000; i--;) DELAY_US(1000UL);
|
||||||
|
ENABLE_ISRS();
|
||||||
|
SERIAL_ECHOLNPGM("FAILURE: Watchdog did not trigger board reset.");
|
||||||
|
} break;
|
||||||
|
|
||||||
|
#if ENABLED(SDSUPPORT)
|
||||||
|
|
||||||
|
case 101: { // D101 Test SD Write
|
||||||
|
card.openFileWrite("test.gco");
|
||||||
|
if (!card.isFileOpen()) {
|
||||||
|
SERIAL_ECHOLNPAIR("Failed to open test.gco to write.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
|
||||||
|
|
||||||
|
uint16_t c;
|
||||||
|
for (c = 0; c < COUNT(buf); c++)
|
||||||
|
buf[c] = 'A' + (c % ('Z' - 'A'));
|
||||||
|
|
||||||
|
c = 1024 * 4;
|
||||||
|
while (c--) {
|
||||||
|
TERN_(USE_WATCHDOG, watchdog_refresh());
|
||||||
|
card.write(buf, COUNT(buf));
|
||||||
|
}
|
||||||
|
SERIAL_ECHOLNPGM(" done");
|
||||||
|
card.closefile();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
#if ENABLED(SDSUPPORT)
|
case 102: { // D102 Test SD Read
|
||||||
|
char testfile[] = "test.gco";
|
||||||
case 101: { // D101 Test SD Write
|
card.openFileRead(testfile);
|
||||||
card.openFileWrite("test.gco");
|
if (!card.isFileOpen()) {
|
||||||
if (!card.isFileOpen()) {
|
SERIAL_ECHOLNPAIR("Failed to open test.gco to read.");
|
||||||
SERIAL_ECHOLNPAIR("Failed to open test.gco to write.");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
__attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
|
||||||
__attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
|
uint16_t c = 1024 * 4;
|
||||||
|
while (c--) {
|
||||||
uint16_t c;
|
TERN_(USE_WATCHDOG, watchdog_refresh());
|
||||||
for (c = 0; c < COUNT(buf); c++)
|
card.read(buf, COUNT(buf));
|
||||||
buf[c] = 'A' + (c % ('Z' - 'A'));
|
bool error = false;
|
||||||
|
for (uint16_t i = 0; i < COUNT(buf); i++) {
|
||||||
c = 1024 * 4;
|
if (buf[i] != ('A' + (i % ('Z' - 'A')))) {
|
||||||
while (c--) {
|
error = true;
|
||||||
TERN_(USE_WATCHDOG, watchdog_refresh());
|
|
||||||
card.write(buf, COUNT(buf));
|
|
||||||
}
|
|
||||||
SERIAL_ECHOLNPGM(" done");
|
|
||||||
card.closefile();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case 102: { // D102 Test SD Read
|
|
||||||
char testfile[] = "test.gco";
|
|
||||||
card.openFileRead(testfile);
|
|
||||||
if (!card.isFileOpen()) {
|
|
||||||
SERIAL_ECHOLNPAIR("Failed to open test.gco to read.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
__attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
|
|
||||||
uint16_t c = 1024 * 4;
|
|
||||||
while (c--) {
|
|
||||||
TERN_(USE_WATCHDOG, watchdog_refresh());
|
|
||||||
card.read(buf, COUNT(buf));
|
|
||||||
bool error = false;
|
|
||||||
for (uint16_t i = 0; i < COUNT(buf); i++) {
|
|
||||||
if (buf[i] != ('A' + (i % ('Z' - 'A')))) {
|
|
||||||
error = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
SERIAL_ECHOLNPGM(" Read error!");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SERIAL_ECHOLNPGM(" done");
|
if (error) {
|
||||||
card.closefile();
|
SERIAL_ECHOLNPGM(" Read error!");
|
||||||
} break;
|
break;
|
||||||
|
|
||||||
#endif // SDSUPPORT
|
|
||||||
|
|
||||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
|
||||||
|
|
||||||
case 451: { // Trigger all kind of faults to test exception catcher
|
|
||||||
SERIAL_ECHOLNPGM("Disabling heaters");
|
|
||||||
thermalManager.disable_all_heaters();
|
|
||||||
delay(1000); // Allow time to print
|
|
||||||
volatile uint8_t type[5] = { parser.byteval('T', 1) };
|
|
||||||
|
|
||||||
// The code below is obviously wrong and it's full of quirks to fool the compiler from optimizing away the code
|
|
||||||
switch (type[0]) {
|
|
||||||
case 1: default: *(int*)0 = 451; break; // Write at bad address
|
|
||||||
case 2: { volatile int a = 0; volatile int b = 452 / a; *(int*)&a = b; } break; // Divide by zero (some CPUs accept this, like ARM)
|
|
||||||
case 3: { *(uint32_t*)&type[1] = 453; volatile int a = *(int*)&type[1]; type[0] = a / 255; } break; // Unaligned access (some CPUs accept this)
|
|
||||||
case 4: { volatile void (*func)() = (volatile void (*)()) 0xE0000000; func(); } break; // Invalid instruction
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
SERIAL_ECHOLNPGM(" done");
|
||||||
|
card.closefile();
|
||||||
|
} break;
|
||||||
|
|
||||||
#endif
|
#endif // SDSUPPORT
|
||||||
}
|
|
||||||
|
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||||
|
|
||||||
|
case 451: { // Trigger all kind of faults to test exception catcher
|
||||||
|
SERIAL_ECHOLNPGM("Disabling heaters");
|
||||||
|
thermalManager.disable_all_heaters();
|
||||||
|
delay(1000); // Allow time to print
|
||||||
|
volatile uint8_t type[5] = { parser.byteval('T', 1) };
|
||||||
|
|
||||||
|
// The code below is obviously wrong and it's full of quirks to fool the compiler from optimizing away the code
|
||||||
|
switch (type[0]) {
|
||||||
|
case 1: default: *(int*)0 = 451; break; // Write at bad address
|
||||||
|
case 2: { volatile int a = 0; volatile int b = 452 / a; *(int*)&a = b; } break; // Divide by zero (some CPUs accept this, like ARM)
|
||||||
|
case 3: { *(uint32_t*)&type[1] = 453; volatile int a = *(int*)&type[1]; type[0] = a / 255; } break; // Unaligned access (some CPUs accept this)
|
||||||
|
case 4: { volatile void (*func)() = (volatile void (*)()) 0xE0000000; func(); } break; // Invalid instruction
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* D576: Return buffer stats or set the auto-report interval.
|
||||||
|
* Usage: D576 [S<seconds>]
|
||||||
|
*
|
||||||
|
* With no parameters emits the following output:
|
||||||
|
* "D576 P<nn> B<nn> PU<nn> PD<nn> BU<nn> BD<nn>"
|
||||||
|
* Where:
|
||||||
|
* P : Planner buffers free
|
||||||
|
* B : Command buffers free
|
||||||
|
* PU: Planner buffer underruns (since the last report)
|
||||||
|
* PD: Longest duration (ms) the planner buffer was empty (since the last report)
|
||||||
|
* BU: Command buffer underruns (since the last report)
|
||||||
|
* BD: Longest duration (ms) command buffer was empty (since the last report)
|
||||||
|
*/
|
||||||
|
case 576: {
|
||||||
|
if (parser.seenval('S'))
|
||||||
|
queue.set_auto_report_interval((uint8_t)parser.value_byte());
|
||||||
|
else
|
||||||
|
queue.report_buffer_statistics();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BUFFER_MONITORING
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif // MARLIN_DEV_MODE
|
||||||
|
|
|
@ -67,6 +67,23 @@ GCodeQueue::RingBuffer GCodeQueue::ring_buffer = { 0 };
|
||||||
static millis_t last_command_time = 0;
|
static millis_t last_command_time = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track buffer underruns
|
||||||
|
*/
|
||||||
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
uint32_t GCodeQueue::command_buffer_underruns = 0,
|
||||||
|
GCodeQueue::planner_buffer_underruns = 0;
|
||||||
|
bool GCodeQueue::command_buffer_empty = false,
|
||||||
|
GCodeQueue::planner_buffer_empty = false;
|
||||||
|
millis_t GCodeQueue::max_command_buffer_empty_duration = 0,
|
||||||
|
GCodeQueue::max_planner_buffer_empty_duration = 0,
|
||||||
|
GCodeQueue::command_buffer_empty_at = 0,
|
||||||
|
GCodeQueue::planner_buffer_empty_at = 0;
|
||||||
|
|
||||||
|
uint8_t GCodeQueue::auto_buffer_report_interval;
|
||||||
|
millis_t GCodeQueue::next_buffer_report_ms;
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serial command injection
|
* Serial command injection
|
||||||
*/
|
*/
|
||||||
|
@ -82,7 +99,6 @@ PGM_P GCodeQueue::injected_commands_P; // = nullptr
|
||||||
*/
|
*/
|
||||||
char GCodeQueue::injected_commands[64]; // = { 0 }
|
char GCodeQueue::injected_commands[64]; // = { 0 }
|
||||||
|
|
||||||
|
|
||||||
void GCodeQueue::RingBuffer::commit_command(bool skip_ok
|
void GCodeQueue::RingBuffer::commit_command(bool skip_ok
|
||||||
OPTARG(HAS_MULTI_SERIAL, serial_index_t serial_ind/*=-1*/)
|
OPTARG(HAS_MULTI_SERIAL, serial_index_t serial_ind/*=-1*/)
|
||||||
) {
|
) {
|
||||||
|
@ -621,7 +637,24 @@ void GCodeQueue::advance() {
|
||||||
if (process_injected_command_P() || process_injected_command()) return;
|
if (process_injected_command_P() || process_injected_command()) return;
|
||||||
|
|
||||||
// Return if the G-code buffer is empty
|
// Return if the G-code buffer is empty
|
||||||
if (ring_buffer.empty()) return;
|
if (ring_buffer.empty()) {
|
||||||
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
if (!command_buffer_empty) {
|
||||||
|
command_buffer_empty = true;
|
||||||
|
command_buffer_underruns++;
|
||||||
|
command_buffer_empty_at = millis();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
if (command_buffer_empty) {
|
||||||
|
command_buffer_empty = false;
|
||||||
|
const millis_t command_buffer_empty_duration = millis() - command_buffer_empty_at;
|
||||||
|
NOLESS(max_command_buffer_empty_duration, command_buffer_empty_duration);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ENABLED(SDSUPPORT)
|
#if ENABLED(SDSUPPORT)
|
||||||
|
|
||||||
|
@ -664,3 +697,41 @@ void GCodeQueue::advance() {
|
||||||
// The queue may be reset by a command handler or by code invoked by idle() within a handler
|
// The queue may be reset by a command handler or by code invoked by idle() within a handler
|
||||||
ring_buffer.advance_pos(ring_buffer.index_r, -1);
|
ring_buffer.advance_pos(ring_buffer.index_r, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
|
||||||
|
void GCodeQueue::report_buffer_statistics() {
|
||||||
|
SERIAL_ECHOLNPAIR("D576"
|
||||||
|
" P:", planner.moves_free(), " ", -queue.planner_buffer_underruns, " (", queue.max_planner_buffer_empty_duration, ")"
|
||||||
|
" B:", BUFSIZE - ring_buffer.length, " ", -queue.command_buffer_underruns, " (", queue.max_command_buffer_empty_duration, ")"
|
||||||
|
);
|
||||||
|
command_buffer_underruns = planner_buffer_underruns = 0;
|
||||||
|
max_command_buffer_empty_duration = max_planner_buffer_empty_duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCodeQueue::auto_report_buffer_statistics() {
|
||||||
|
// Bit of a hack to try to catch planner buffer underruns without having logic
|
||||||
|
// running inside Stepper::block_phase_isr
|
||||||
|
const millis_t ms = millis();
|
||||||
|
if (planner.movesplanned() == 0) {
|
||||||
|
if (!planner_buffer_empty) { // the planner buffer wasn't empty, but now it is
|
||||||
|
planner_buffer_empty = true;
|
||||||
|
planner_buffer_underruns++;
|
||||||
|
planner_buffer_empty_at = ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (planner_buffer_empty) { // the planner buffer was empty, but now it's not
|
||||||
|
planner_buffer_empty = false;
|
||||||
|
const millis_t planner_buffer_empty_duration = ms - planner_buffer_empty_at;
|
||||||
|
NOLESS(max_planner_buffer_empty_duration, planner_buffer_empty_duration); // if it's longer than the currently tracked max duration, replace it
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue.auto_buffer_report_interval && ELAPSED(ms, queue.next_buffer_report_ms)) {
|
||||||
|
queue.next_buffer_report_ms = ms + 1000UL * queue.auto_buffer_report_interval;
|
||||||
|
PORT_REDIRECT(SERIAL_BOTH);
|
||||||
|
report_buffer_statistics();
|
||||||
|
PORT_RESTORE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BUFFER_MONITORING
|
||||||
|
|
|
@ -197,6 +197,46 @@ public:
|
||||||
*/
|
*/
|
||||||
static inline void set_current_line_number(long n) { serial_state[ring_buffer.command_port().index].last_N = n; }
|
static inline void set_current_line_number(long n) { serial_state[ring_buffer.command_port().index].last_N = n; }
|
||||||
|
|
||||||
|
#if ENABLED(BUFFER_MONITORING)
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track buffer underruns
|
||||||
|
*/
|
||||||
|
static uint32_t command_buffer_underruns, planner_buffer_underruns;
|
||||||
|
static bool command_buffer_empty, planner_buffer_empty;
|
||||||
|
static millis_t max_command_buffer_empty_duration, max_planner_buffer_empty_duration,
|
||||||
|
command_buffer_empty_at, planner_buffer_empty_at;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report buffer statistics to the host to be able to detect buffer underruns
|
||||||
|
*
|
||||||
|
* Returns "D576 " followed by:
|
||||||
|
* P<uint> Planner space remaining
|
||||||
|
* B<uint> Command buffer space remaining
|
||||||
|
* PU<uint> Number of planner buffer underruns since last report
|
||||||
|
* PD<uint> Max time in ms the planner buffer was empty since last report
|
||||||
|
* BU<uint> Number of command buffer underruns since last report
|
||||||
|
* BD<uint> Max time in ms the command buffer was empty since last report
|
||||||
|
*/
|
||||||
|
static void report_buffer_statistics();
|
||||||
|
|
||||||
|
static uint8_t auto_buffer_report_interval;
|
||||||
|
static millis_t next_buffer_report_ms;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static void auto_report_buffer_statistics();
|
||||||
|
|
||||||
|
static inline void set_auto_report_interval(uint8_t v) {
|
||||||
|
NOMORE(v, 60);
|
||||||
|
auto_buffer_report_interval = v;
|
||||||
|
next_buffer_report_ms = millis() + 1000UL * v;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BUFFER_MONITORING
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static void get_serial_commands();
|
static void get_serial_commands();
|
||||||
|
|
|
@ -10,7 +10,7 @@ set -e
|
||||||
# Build with configs included in the PR
|
# Build with configs included in the PR
|
||||||
#
|
#
|
||||||
use_example_configs "Creality/Ender-3 V2/CrealityUI"
|
use_example_configs "Creality/Ender-3 V2/CrealityUI"
|
||||||
opt_enable MARLIN_DEV_MODE
|
opt_enable MARLIN_DEV_MODE BUFFER_MONITORING
|
||||||
exec_test $1 $2 "Ender 3 v2 with CrealityUI" "$3"
|
exec_test $1 $2 "Ender 3 v2 with CrealityUI" "$3"
|
||||||
|
|
||||||
use_example_configs "Creality/Ender-3 V2/CrealityUI"
|
use_example_configs "Creality/Ender-3 V2/CrealityUI"
|
||||||
|
|
Loading…
Reference in a new issue