diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 485adae4f28c..6cb70ffa9376 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2211,6 +2211,14 @@
// Some clients will have this feature soon. This could make the NO_TIMEOUTS unnecessary.
#define ADVANCED_OK
+/**
+ * Buffer monitoring
+ *
+ * To help diagnose print quality issues stemming from command buffers being empty,
+ * we add M576 which enables reporting of buffer empty
+ */
+//#define BUFFER_MONITORING
+
// Printrun may have trouble receiving long strings all at once.
// This option inserts short delays between lines of serial output.
#define SERIAL_OVERRUN_PROTECTION
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index ac3b5010b980..d8122284de24 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -851,6 +851,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 575: M575(); break; // M575: Set serial baudrate
#endif
+ #if ENABLED(BUFFER_MONITORING) // M576: Buffer monitoring
+ case 576: M576(); break;
+ #endif
+
#if ENABLED(ADVANCED_PAUSE_FEATURE)
case 600: M600(); break; // M600: Pause for Filament Change
case 603: M603(); break; // M603: Configure Filament Change
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index 752a3da9dc63..dd11ea36c7da 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -972,6 +972,8 @@ class GcodeSuite {
static void M575();
#endif
+ TERN_(BUFFER_MONITORING, static void M576());
+
#if ENABLED(ADVANCED_PAUSE_FEATURE)
static void M600();
static void M603();
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index 09755fbf21a8..a9ad8cd01b7c 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -68,8 +68,22 @@ GCodeQueue::RingBuffer GCodeQueue::ring_buffer = { 0 };
#endif
/**
- * Serial command injection
+ * Track buffer underruns
*/
+#if ENABLED(BUFFER_MONITORING)
+ uint32_t GCodeQueue::command_buffer_underruns = 0;
+ bool GCodeQueue::command_buffer_empty = false;
+ millis_t GCodeQueue::max_command_buffer_empty_duration = 0;
+ millis_t GCodeQueue::command_buffer_empty_at = 0;
+
+ uint32_t GCodeQueue::planner_buffer_underruns = 0;
+ bool GCodeQueue::planner_buffer_empty = false;
+ millis_t GCodeQueue::max_planner_buffer_empty_duration = 0;
+ millis_t GCodeQueue::planner_buffer_empty_at = 0;
+
+ uint8_t GCodeQueue::auto_buffer_report_interval;
+ millis_t GCodeQueue::next_buffer_report_ms;
+#endif
/**
* Next Injected PROGMEM Command pointer. (nullptr == empty)
@@ -621,7 +635,27 @@ void GCodeQueue::advance() {
if (process_injected_command_P() || process_injected_command()) return;
// 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) {
+ static millis_t command_buffer_empty_duration;
+ command_buffer_empty_duration = millis() - command_buffer_empty_at;
+ if (command_buffer_empty_duration > max_command_buffer_empty_duration) {
+ max_command_buffer_empty_duration = command_buffer_empty_duration;
+ }
+ command_buffer_empty = false;
+ }
+ #endif
#if ENABLED(SDSUPPORT)
@@ -664,3 +698,52 @@ void GCodeQueue::advance() {
// 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);
}
+
+#if ENABLED(BUFFER_MONITORING)
+void GCodeQueue::report_buffer_statistics() {
+ SERIAL_ECHO("M576");
+ SERIAL_ECHOLNPAIR(SP_P_STR, int(planner.moves_free()),
+ SP_B_STR, int(BUFSIZE - length),
+ " PU", queue.planner_buffer_underruns,
+ " PD", queue.max_planner_buffer_empty_duration,
+ " BU", queue.command_buffer_underruns,
+ " BD", queue.max_command_buffer_empty_duration,
+ );
+
+ command_buffer_underruns = 0;
+ max_command_buffer_empty_duration = 0;
+
+ planner_buffer_underruns = 0;
+ 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
+ if (planner.movesplanned() == 0) {
+ if (!planner_buffer_empty) { // if the planner buffer wasn't empty, but now it is
+ planner_buffer_empty = true;
+ planner_buffer_underruns++;
+ planner_buffer_empty_at = millis();
+ }
+ } else if (planner_buffer_empty) { // if the planner buffer was empty, but now it ain't
+ static millis_t planner_buffer_empty_duration;
+ planner_buffer_empty_duration = millis() - planner_buffer_empty_at;
+
+ // if it's longer than the currently tracked max duration, replace it
+ if (planner_buffer_empty_duration > max_planner_buffer_empty_duration) {
+ max_planner_buffer_empty_duration = planner_buffer_empty_duration;
+ }
+
+ planner_buffer_empty = false;
+ }
+
+ if (queue.auto_buffer_report_interval && ELAPSED(millis(), queue.next_buffer_report_ms)) {
+ queue.next_buffer_report_ms = millis() + 1000UL * queue.auto_buffer_report_interval;
+ PORT_REDIRECT(SERIAL_BOTH);
+ report_buffer_statistics();
+ PORT_RESTORE();
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Marlin/src/gcode/queue.h b/Marlin/src/gcode/queue.h
index f4898556e244..1cd134347246 100644
--- a/Marlin/src/gcode/queue.h
+++ b/Marlin/src/gcode/queue.h
@@ -236,8 +236,6 @@ class GCodeQueue {
*/
static inline void set_current_line_number(long n) { serial_state[ring_buffer.command_port().index].last_N = n; }
-private:
-
static void get_serial_commands();
#if ENABLED(SDSUPPORT)
diff --git a/Marlin/src/gcode/stats/M576.cpp b/Marlin/src/gcode/stats/M576.cpp
new file mode 100644
index 000000000000..dfa9a1cad073
--- /dev/null
+++ b/Marlin/src/gcode/stats/M576.cpp
@@ -0,0 +1,54 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+#include "../../MarlinCore.h" // for SP_P/B_STR, etc.
+
+#if ENABLED(BUFFER_MONITORING)
+
+#include "../gcode.h"
+#include "../queue.h"
+
+/**
+ * M576: Return buffer stats, and optionally set auto-report interval.
+ * Usage: M576 [S]
+ *
+ * When called, printer emits the following output:
+ * "M576 P B PU PD BU BD"
+ * Where:
+ * P: Planner buffers available
+ * B: Command buffers available
+ * PU: Planner buffer underruns since last report
+ * PD: Maximum time in ms planner buffer was empty since last report
+ * BU: Command buffer underruns since last report
+ * BD: Maximum time in ms command buffer was empty since last report
+ */
+void GcodeSuite::M576() {
+ if (parser.seenval('S')) {
+ queue.set_auto_report_interval((uint8_t)parser.value_byte());
+ }
+
+ queue.report_buffer_statistics();
+}
+
+#endif // BUFFER_MONITORING