Bläddra i källkod

✨ D576 Buffer Monitoring (#19674)

chendo 2 år sedan
förälder
incheckning
2c49283e97
Inget konto är kopplat till bidragsgivarens mejladress

+ 8
- 0
Marlin/Configuration_adv.h Visa fil

@@ -4174,6 +4174,14 @@
4174 4174
 // Enable Marlin dev mode which adds some special commands
4175 4175
 //#define MARLIN_DEV_MODE
4176 4176
 
4177
+#if ENABLED(MARLIN_DEV_MODE)
4178
+  /**
4179
+   * D576 - Buffer Monitoring
4180
+   * To help diagnose print quality issues stemming from empty command buffers.
4181
+   */
4182
+  //#define BUFFER_MONITORING
4183
+#endif
4184
+
4177 4185
 /**
4178 4186
  * Postmortem Debugging captures misbehavior and outputs the CPU status and backtrace to serial.
4179 4187
  * When running in the debugger it will break for debugging. This is useful to help understand

+ 1
- 0
Marlin/src/MarlinCore.cpp Visa fil

@@ -868,6 +868,7 @@ void idle(bool no_stepper_sleep/*=false*/) {
868 868
       TERN_(AUTO_REPORT_TEMPERATURES, thermalManager.auto_reporter.tick());
869 869
       TERN_(AUTO_REPORT_SD_STATUS, card.auto_reporter.tick());
870 870
       TERN_(AUTO_REPORT_POSITION, position_auto_reporter.tick());
871
+      TERN_(BUFFER_MONITORING, queue.auto_report_buffer_statistics());
871 872
     }
872 873
   #endif
873 874
 

+ 2
- 0
Marlin/src/gcode/gcode.h Visa fil

@@ -241,6 +241,7 @@
241 241
  * M553 - Get or set IP netmask. (Requires enabled Ethernet port)
242 242
  * M554 - Get or set IP gateway. (Requires enabled Ethernet port)
243 243
  * M569 - Enable stealthChop on an axis. (Requires at least one _DRIVER_TYPE to be TMC2130/2160/2208/2209/5130/5160)
244
+ * M575 - Change the serial baud rate. (Requires BAUD_RATE_GCODE)
244 245
  * M600 - Pause for filament change: "M600 X<pos> Y<pos> Z<raise> E<first_retract> L<later_retract>". (Requires ADVANCED_PAUSE_FEATURE)
245 246
  * M603 - Configure filament change: "M603 T<tool> U<unload_length> L<load_length>". (Requires ADVANCED_PAUSE_FEATURE)
246 247
  * M605 - Set Dual X-Carriage movement mode: "M605 S<mode> [X<x_offset>] [R<temp_offset>]". (Requires DUAL_X_CARRIAGE)
@@ -297,6 +298,7 @@
297 298
  * M997 - Perform in-application firmware update
298 299
  * M999 - Restart after being stopped by error
299 300
  * D... - Custom Development G-code. Add hooks to 'gcode_D.cpp' for developers to test features. (Requires MARLIN_DEV_MODE)
301
+ *        D576 - Set buffer monitoring options. (Requires BUFFER_MONITORING)
300 302
  *
301 303
  * "T" Codes
302 304
  *

+ 235
- 204
Marlin/src/gcode/gcode_d.cpp Visa fil

@@ -23,248 +23,279 @@
23 23
 
24 24
 #if ENABLED(MARLIN_DEV_MODE)
25 25
 
26
-  #include "gcode.h"
27
-  #include "../module/settings.h"
28
-  #include "../module/temperature.h"
29
-  #include "../libs/hex_print.h"
30
-  #include "../HAL/shared/eeprom_if.h"
31
-  #include "../HAL/shared/Delay.h"
32
-  #include "../sd/cardreader.h"
33
-  #include "../MarlinCore.h" // for kill
26
+#include "gcode.h"
34 27
 
35
-  extern void dump_delay_accuracy_check();
28
+#if ENABLED(BUFFER_MONITORING)
29
+  #include "queue.h"
30
+#endif
36 31
 
37
-  /**
38
-   * Dn: G-code for development and testing
39
-   *
40
-   * See https://reprap.org/wiki/G-code#D:_Debug_codes
41
-   *
42
-   * Put whatever else you need here to test ongoing development.
43
-   */
44
-  void GcodeSuite::D(const int16_t dcode) {
45
-    switch (dcode) {
32
+#include "../module/settings.h"
33
+#include "../module/temperature.h"
34
+#include "../libs/hex_print.h"
35
+#include "../HAL/shared/eeprom_if.h"
36
+#include "../HAL/shared/Delay.h"
37
+#include "../sd/cardreader.h"
38
+#include "../MarlinCore.h" // for kill
46 39
 
47
-      case -1:
48
-        for (;;) { /* loop forever (watchdog reset) */ }
40
+extern void dump_delay_accuracy_check();
49 41
 
50
-      case 0:
51
-        HAL_reboot();
52
-        break;
42
+/**
43
+ * Dn: G-code for development and testing
44
+ *
45
+ * See https://reprap.org/wiki/G-code#D:_Debug_codes
46
+ *
47
+ * Put whatever else you need here to test ongoing development.
48
+ */
49
+void GcodeSuite::D(const int16_t dcode) {
50
+  switch (dcode) {
53 51
 
54
-      case 10:
55
-        kill(PSTR("D10"), PSTR("KILL TEST"), parser.seen_test('P'));
56
-        break;
52
+    case -1:
53
+      for (;;) { /* loop forever (watchdog reset) */ }
57 54
 
58
-      case 1: {
59
-        // Zero or pattern-fill the EEPROM data
60
-        #if ENABLED(EEPROM_SETTINGS)
61
-          persistentStore.access_start();
62
-          size_t total = persistentStore.capacity();
63
-          int pos = 0;
64
-          const uint8_t value = 0x0;
65
-          while (total--) persistentStore.write_data(pos, &value, 1);
66
-          persistentStore.access_finish();
67
-        #else
68
-          settings.reset();
69
-          settings.save();
70
-        #endif
71
-        HAL_reboot();
72
-      } break;
55
+    case 0:
56
+      HAL_reboot();
57
+      break;
73 58
 
74
-      case 2: { // D2 Read / Write SRAM
75
-        #define SRAM_SIZE 8192
76
-        uint8_t *pointer = parser.hex_adr_val('A');
77
-        uint16_t len = parser.ushortval('C', 1);
78
-        uintptr_t addr = (uintptr_t)pointer;
79
-        NOMORE(addr, size_t(SRAM_SIZE - 1));
80
-        NOMORE(len, SRAM_SIZE - addr);
81
-        if (parser.seenval('X')) {
82
-          // Write the hex bytes after the X
83
-          uint16_t val = parser.hex_val('X');
84
-          while (len--) {
85
-            *pointer = val;
86
-            pointer++;
87
-          }
88
-        }
89
-        else {
90
-          while (len--) print_hex_byte(*(pointer++));
91
-          SERIAL_EOL();
92
-        }
93
-      } break;
59
+    case 10:
60
+      kill(PSTR("D10"), PSTR("KILL TEST"), parser.seen_test('P'));
61
+      break;
94 62
 
63
+    case 1: {
64
+      // Zero or pattern-fill the EEPROM data
95 65
       #if ENABLED(EEPROM_SETTINGS)
96
-        case 3: { // D3 Read / Write EEPROM
97
-          uint8_t *pointer = parser.hex_adr_val('A');
98
-          uint16_t len = parser.ushortval('C', 1);
99
-          uintptr_t addr = (uintptr_t)pointer;
100
-          NOMORE(addr, size_t(persistentStore.capacity() - 1));
101
-          NOMORE(len, persistentStore.capacity() - addr);
102
-          if (parser.seenval('X')) {
103
-            uint16_t val = parser.hex_val('X');
104
-            #if ENABLED(EEPROM_SETTINGS)
105
-              persistentStore.access_start();
106
-              while (len--) {
107
-                int pos = 0;
108
-                persistentStore.write_data(pos, (uint8_t *)&val, sizeof(val));
109
-              }
110
-              SERIAL_EOL();
111
-              persistentStore.access_finish();
112
-            #else
113
-              SERIAL_ECHOLNPGM("NO EEPROM");
114
-            #endif
115
-          }
116
-          else {
117
-            // Read bytes from EEPROM
118
-            #if ENABLED(EEPROM_SETTINGS)
119
-              persistentStore.access_start();
120
-              int pos = 0;
121
-              uint8_t val;
122
-              while (len--) if (!persistentStore.read_data(pos, &val, 1)) print_hex_byte(val);
123
-              SERIAL_EOL();
124
-              persistentStore.access_finish();
125
-            #else
126
-              SERIAL_ECHOLNPGM("NO EEPROM");
127
-              len = 0;
128
-            #endif
129
-            SERIAL_EOL();
130
-          }
131
-        } break;
66
+        persistentStore.access_start();
67
+        size_t total = persistentStore.capacity();
68
+        int pos = 0;
69
+        const uint8_t value = 0x0;
70
+        while (total--) persistentStore.write_data(pos, &value, 1);
71
+        persistentStore.access_finish();
72
+      #else
73
+        settings.reset();
74
+        settings.save();
132 75
       #endif
76
+      HAL_reboot();
77
+    } break;
133 78
 
134
-      case 4: { // D4 Read / Write PIN
135
-        //const bool is_out = parser.boolval('F');
136
-        //const uint8_t pin = parser.byteval('P'),
137
-        //              val = parser.byteval('V', LOW);
138
-        if (parser.seenval('X')) {
139
-          // TODO: Write the hex bytes after the X
140
-          //while (len--) {
141
-          //}
79
+    case 2: { // D2 Read / Write SRAM
80
+      #define SRAM_SIZE 8192
81
+      uint8_t *pointer = parser.hex_adr_val('A');
82
+      uint16_t len = parser.ushortval('C', 1);
83
+      uintptr_t addr = (uintptr_t)pointer;
84
+      NOMORE(addr, size_t(SRAM_SIZE - 1));
85
+      NOMORE(len, SRAM_SIZE - addr);
86
+      if (parser.seenval('X')) {
87
+        // Write the hex bytes after the X
88
+        uint16_t val = parser.hex_val('X');
89
+        while (len--) {
90
+          *pointer = val;
91
+          pointer++;
142 92
         }
143
-        else {
144
-          //while (len--) {
145
-          //// TODO: Read bytes from EEPROM
146
-          //  print_hex_byte(eeprom_read_byte(adr++));
147
-          //}
148
-          SERIAL_EOL();
149
-        }
150
-      } break;
93
+      }
94
+      else {
95
+        while (len--) print_hex_byte(*(pointer++));
96
+        SERIAL_EOL();
97
+      }
98
+    } break;
151 99
 
152
-      case 5: { // D5 Read / Write onboard Flash
153
-        #define FLASH_SIZE 1024
100
+    #if ENABLED(EEPROM_SETTINGS)
101
+      case 3: { // D3 Read / Write EEPROM
154 102
         uint8_t *pointer = parser.hex_adr_val('A');
155 103
         uint16_t len = parser.ushortval('C', 1);
156 104
         uintptr_t addr = (uintptr_t)pointer;
157
-        NOMORE(addr, size_t(FLASH_SIZE - 1));
158
-        NOMORE(len, FLASH_SIZE - addr);
105
+        NOMORE(addr, size_t(persistentStore.capacity() - 1));
106
+        NOMORE(len, persistentStore.capacity() - addr);
159 107
         if (parser.seenval('X')) {
160
-          // TODO: Write the hex bytes after the X
161
-          //while (len--) {}
108
+          uint16_t val = parser.hex_val('X');
109
+          #if ENABLED(EEPROM_SETTINGS)
110
+            persistentStore.access_start();
111
+            while (len--) {
112
+              int pos = 0;
113
+              persistentStore.write_data(pos, (uint8_t *)&val, sizeof(val));
114
+            }
115
+            SERIAL_EOL();
116
+            persistentStore.access_finish();
117
+          #else
118
+            SERIAL_ECHOLNPGM("NO EEPROM");
119
+          #endif
162 120
         }
163 121
         else {
164
-          //while (len--) {
165
-          //// TODO: Read bytes from EEPROM
166
-          //  print_hex_byte(eeprom_read_byte(adr++));
167
-          //}
122
+          // Read bytes from EEPROM
123
+          #if ENABLED(EEPROM_SETTINGS)
124
+            persistentStore.access_start();
125
+            int pos = 0;
126
+            uint8_t val;
127
+            while (len--) if (!persistentStore.read_data(pos, &val, 1)) print_hex_byte(val);
128
+            SERIAL_EOL();
129
+            persistentStore.access_finish();
130
+          #else
131
+            SERIAL_ECHOLNPGM("NO EEPROM");
132
+            len = 0;
133
+          #endif
168 134
           SERIAL_EOL();
169 135
         }
170 136
       } break;
137
+    #endif
171 138
 
172
-      case 6: // D6 Check delay loop accuracy
173
-        dump_delay_accuracy_check();
174
-        break;
139
+    case 4: { // D4 Read / Write PIN
140
+      //const bool is_out = parser.boolval('F');
141
+      //const uint8_t pin = parser.byteval('P'),
142
+      //              val = parser.byteval('V', LOW);
143
+      if (parser.seenval('X')) {
144
+        // TODO: Write the hex bytes after the X
145
+        //while (len--) {
146
+        //}
147
+      }
148
+      else {
149
+        //while (len--) {
150
+        //// TODO: Read bytes from EEPROM
151
+        //  print_hex_byte(eeprom_read_byte(adr++));
152
+        //}
153
+        SERIAL_EOL();
154
+      }
155
+    } break;
175 156
 
176
-      case 7: // D7 dump the current serial port type (hence configuration)
177
-        SERIAL_ECHOLNPAIR("Current serial configuration RX_BS:", RX_BUFFER_SIZE, ", TX_BS:", TX_BUFFER_SIZE);
178
-        SERIAL_ECHOLN(gtn(&SERIAL_IMPL));
179
-        break;
157
+    case 5: { // D5 Read / Write onboard Flash
158
+      #define FLASH_SIZE 1024
159
+      uint8_t *pointer = parser.hex_adr_val('A');
160
+      uint16_t len = parser.ushortval('C', 1);
161
+      uintptr_t addr = (uintptr_t)pointer;
162
+      NOMORE(addr, size_t(FLASH_SIZE - 1));
163
+      NOMORE(len, FLASH_SIZE - addr);
164
+      if (parser.seenval('X')) {
165
+        // TODO: Write the hex bytes after the X
166
+        //while (len--) {}
167
+      }
168
+      else {
169
+        //while (len--) {
170
+        //// TODO: Read bytes from EEPROM
171
+        //  print_hex_byte(eeprom_read_byte(adr++));
172
+        //}
173
+        SERIAL_EOL();
174
+      }
175
+    } break;
180 176
 
181
-      case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test)
182
-        SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog");
183
-        SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")");
184
-        thermalManager.disable_all_heaters();
185
-        delay(1000); // Allow time to print
186
-        DISABLE_ISRS();
187
-        // Use a low-level delay that does not rely on interrupts to function
188
-        // Do not spin forever, to avoid thermal risks if heaters are enabled and
189
-        // watchdog does not work.
190
-        for (int i = 10000; i--;) DELAY_US(1000UL);
191
-        ENABLE_ISRS();
192
-        SERIAL_ECHOLNPGM("FAILURE: Watchdog did not trigger board reset.");
193
-      } break;
177
+    case 6: // D6 Check delay loop accuracy
178
+      dump_delay_accuracy_check();
179
+      break;
194 180
 
195
-      #if ENABLED(SDSUPPORT)
181
+    case 7: // D7 dump the current serial port type (hence configuration)
182
+      SERIAL_ECHOLNPAIR("Current serial configuration RX_BS:", RX_BUFFER_SIZE, ", TX_BS:", TX_BUFFER_SIZE);
183
+      SERIAL_ECHOLN(gtn(&SERIAL_IMPL));
184
+      break;
196 185
 
197
-        case 101: { // D101 Test SD Write
198
-          card.openFileWrite("test.gco");
199
-          if (!card.isFileOpen()) {
200
-            SERIAL_ECHOLNPAIR("Failed to open test.gco to write.");
201
-            return;
202
-          }
203
-          __attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
186
+    case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test)
187
+      SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog");
188
+      SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")");
189
+      thermalManager.disable_all_heaters();
190
+      delay(1000); // Allow time to print
191
+      DISABLE_ISRS();
192
+      // Use a low-level delay that does not rely on interrupts to function
193
+      // Do not spin forever, to avoid thermal risks if heaters are enabled and
194
+      // watchdog does not work.
195
+      for (int i = 10000; i--;) DELAY_US(1000UL);
196
+      ENABLE_ISRS();
197
+      SERIAL_ECHOLNPGM("FAILURE: Watchdog did not trigger board reset.");
198
+    } break;
204 199
 
205
-          uint16_t c;
206
-          for (c = 0; c < COUNT(buf); c++)
207
-            buf[c] = 'A' + (c % ('Z' - 'A'));
200
+    #if ENABLED(SDSUPPORT)
208 201
 
209
-          c = 1024 * 4;
210
-          while (c--) {
211
-            TERN_(USE_WATCHDOG, watchdog_refresh());
212
-            card.write(buf, COUNT(buf));
213
-          }
214
-          SERIAL_ECHOLNPGM(" done");
215
-          card.closefile();
216
-        } break;
202
+      case 101: { // D101 Test SD Write
203
+        card.openFileWrite("test.gco");
204
+        if (!card.isFileOpen()) {
205
+          SERIAL_ECHOLNPAIR("Failed to open test.gco to write.");
206
+          return;
207
+        }
208
+        __attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
217 209
 
218
-        case 102: { // D102 Test SD Read
219
-          char testfile[] = "test.gco";
220
-          card.openFileRead(testfile);
221
-          if (!card.isFileOpen()) {
222
-            SERIAL_ECHOLNPAIR("Failed to open test.gco to read.");
223
-            return;
224
-          }
225
-          __attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
226
-          uint16_t c = 1024 * 4;
227
-          while (c--) {
228
-            TERN_(USE_WATCHDOG, watchdog_refresh());
229
-            card.read(buf, COUNT(buf));
230
-            bool error = false;
231
-            for (uint16_t i = 0; i < COUNT(buf); i++) {
232
-              if (buf[i] != ('A' + (i % ('Z' - 'A')))) {
233
-                error = true;
234
-                break;
235
-              }
236
-            }
237
-            if (error) {
238
-              SERIAL_ECHOLNPGM(" Read error!");
210
+        uint16_t c;
211
+        for (c = 0; c < COUNT(buf); c++)
212
+          buf[c] = 'A' + (c % ('Z' - 'A'));
213
+
214
+        c = 1024 * 4;
215
+        while (c--) {
216
+          TERN_(USE_WATCHDOG, watchdog_refresh());
217
+          card.write(buf, COUNT(buf));
218
+        }
219
+        SERIAL_ECHOLNPGM(" done");
220
+        card.closefile();
221
+      } break;
222
+
223
+      case 102: { // D102 Test SD Read
224
+        char testfile[] = "test.gco";
225
+        card.openFileRead(testfile);
226
+        if (!card.isFileOpen()) {
227
+          SERIAL_ECHOLNPAIR("Failed to open test.gco to read.");
228
+          return;
229
+        }
230
+        __attribute__((aligned(sizeof(size_t)))) uint8_t buf[512];
231
+        uint16_t c = 1024 * 4;
232
+        while (c--) {
233
+          TERN_(USE_WATCHDOG, watchdog_refresh());
234
+          card.read(buf, COUNT(buf));
235
+          bool error = false;
236
+          for (uint16_t i = 0; i < COUNT(buf); i++) {
237
+            if (buf[i] != ('A' + (i % ('Z' - 'A')))) {
238
+              error = true;
239 239
               break;
240 240
             }
241 241
           }
242
-          SERIAL_ECHOLNPGM(" done");
243
-          card.closefile();
244
-        } break;
242
+          if (error) {
243
+            SERIAL_ECHOLNPGM(" Read error!");
244
+            break;
245
+          }
246
+        }
247
+        SERIAL_ECHOLNPGM(" done");
248
+        card.closefile();
249
+      } break;
245 250
 
246
-      #endif // SDSUPPORT
251
+    #endif // SDSUPPORT
247 252
 
248
-      #if ENABLED(POSTMORTEM_DEBUGGING)
253
+    #if ENABLED(POSTMORTEM_DEBUGGING)
249 254
 
250
-        case 451: { // Trigger all kind of faults to test exception catcher
251
-          SERIAL_ECHOLNPGM("Disabling heaters");
252
-          thermalManager.disable_all_heaters();
253
-          delay(1000); // Allow time to print
254
-          volatile uint8_t type[5] = { parser.byteval('T', 1) };
255
+      case 451: { // Trigger all kind of faults to test exception catcher
256
+        SERIAL_ECHOLNPGM("Disabling heaters");
257
+        thermalManager.disable_all_heaters();
258
+        delay(1000); // Allow time to print
259
+        volatile uint8_t type[5] = { parser.byteval('T', 1) };
255 260
 
256
-          // The code below is obviously wrong and it's full of quirks to fool the compiler from optimizing away the code
257
-          switch (type[0]) {
258
-            case 1: default: *(int*)0 = 451; break; // Write at bad address
259
-            case 2: { volatile int a = 0; volatile int b = 452 / a; *(int*)&a = b; } break; // Divide by zero (some CPUs accept this, like ARM)
260
-            case 3: { *(uint32_t*)&type[1] = 453; volatile int a = *(int*)&type[1]; type[0] = a / 255; } break; // Unaligned access (some CPUs accept this)
261
-            case 4: { volatile void (*func)() = (volatile void (*)()) 0xE0000000; func(); } break; // Invalid instruction
262
-          }
263
-          break;
261
+        // The code below is obviously wrong and it's full of quirks to fool the compiler from optimizing away the code
262
+        switch (type[0]) {
263
+          case 1: default: *(int*)0 = 451; break; // Write at bad address
264
+          case 2: { volatile int a = 0; volatile int b = 452 / a; *(int*)&a = b; } break; // Divide by zero (some CPUs accept this, like ARM)
265
+          case 3: { *(uint32_t*)&type[1] = 453; volatile int a = *(int*)&type[1]; type[0] = a / 255; } break; // Unaligned access (some CPUs accept this)
266
+          case 4: { volatile void (*func)() = (volatile void (*)()) 0xE0000000; func(); } break; // Invalid instruction
264 267
         }
268
+        break;
269
+      }
265 270
 
266
-      #endif
267
-    }
271
+    #endif
272
+
273
+    #if ENABLED(BUFFER_MONITORING)
274
+
275
+      /**
276
+       * D576: Return buffer stats or set the auto-report interval.
277
+       * Usage: D576 [S<seconds>]
278
+       *
279
+       * With no parameters emits the following output:
280
+       * "D576 P<nn> B<nn> PU<nn> PD<nn> BU<nn> BD<nn>"
281
+       * Where:
282
+       *   P : Planner buffers free
283
+       *   B : Command buffers free
284
+       *   PU: Planner buffer underruns (since the last report)
285
+       *   PD: Longest duration (ms) the planner buffer was empty (since the last report)
286
+       *   BU: Command buffer underruns (since the last report)
287
+       *   BD: Longest duration (ms) command buffer was empty (since the last report)
288
+       */
289
+      case 576: {
290
+        if (parser.seenval('S'))
291
+          queue.set_auto_report_interval((uint8_t)parser.value_byte());
292
+        else
293
+          queue.report_buffer_statistics();
294
+        break;
295
+      }
296
+
297
+    #endif // BUFFER_MONITORING
268 298
   }
299
+}
269 300
 
270
-#endif
301
+#endif // MARLIN_DEV_MODE

+ 73
- 2
Marlin/src/gcode/queue.cpp Visa fil

@@ -68,6 +68,23 @@ GCodeQueue::RingBuffer GCodeQueue::ring_buffer = { 0 };
68 68
 #endif
69 69
 
70 70
 /**
71
+ * Track buffer underruns
72
+ */
73
+#if ENABLED(BUFFER_MONITORING)
74
+  uint32_t GCodeQueue::command_buffer_underruns = 0,
75
+           GCodeQueue::planner_buffer_underruns = 0;
76
+  bool GCodeQueue::command_buffer_empty = false,
77
+       GCodeQueue::planner_buffer_empty = false;
78
+  millis_t GCodeQueue::max_command_buffer_empty_duration = 0,
79
+           GCodeQueue::max_planner_buffer_empty_duration = 0,
80
+           GCodeQueue::command_buffer_empty_at = 0,
81
+           GCodeQueue::planner_buffer_empty_at = 0;
82
+
83
+  uint8_t GCodeQueue::auto_buffer_report_interval;
84
+  millis_t GCodeQueue::next_buffer_report_ms;
85
+#endif
86
+
87
+/**
71 88
  * Serial command injection
72 89
  */
73 90
 
@@ -82,7 +99,6 @@ PGM_P GCodeQueue::injected_commands_P; // = nullptr
82 99
  */
83 100
 char GCodeQueue::injected_commands[64]; // = { 0 }
84 101
 
85
-
86 102
 void GCodeQueue::RingBuffer::commit_command(bool skip_ok
87 103
   OPTARG(HAS_MULTI_SERIAL, serial_index_t serial_ind/*=-1*/)
88 104
 ) {
@@ -621,7 +637,24 @@ void GCodeQueue::advance() {
621 637
   if (process_injected_command_P() || process_injected_command()) return;
622 638
 
623 639
   // Return if the G-code buffer is empty
624
-  if (ring_buffer.empty()) return;
640
+  if (ring_buffer.empty()) {
641
+    #if ENABLED(BUFFER_MONITORING)
642
+      if (!command_buffer_empty) {
643
+        command_buffer_empty = true;
644
+        command_buffer_underruns++;
645
+        command_buffer_empty_at = millis();
646
+      }
647
+    #endif
648
+    return;
649
+  }
650
+
651
+  #if ENABLED(BUFFER_MONITORING)
652
+    if (command_buffer_empty) {
653
+      command_buffer_empty = false;
654
+      const millis_t command_buffer_empty_duration = millis() - command_buffer_empty_at;
655
+      NOLESS(max_command_buffer_empty_duration, command_buffer_empty_duration);
656
+    }
657
+  #endif
625 658
 
626 659
   #if ENABLED(SDSUPPORT)
627 660
 
@@ -664,3 +697,41 @@ void GCodeQueue::advance() {
664 697
   // The queue may be reset by a command handler or by code invoked by idle() within a handler
665 698
   ring_buffer.advance_pos(ring_buffer.index_r, -1);
666 699
 }
700
+
701
+#if ENABLED(BUFFER_MONITORING)
702
+
703
+  void GCodeQueue::report_buffer_statistics() {
704
+    SERIAL_ECHOLNPAIR("D576"
705
+      " P:", planner.moves_free(),         " ", -queue.planner_buffer_underruns, " (", queue.max_planner_buffer_empty_duration, ")"
706
+      " B:", BUFSIZE - ring_buffer.length, " ", -queue.command_buffer_underruns, " (", queue.max_command_buffer_empty_duration, ")"
707
+    );
708
+    command_buffer_underruns = planner_buffer_underruns = 0;
709
+    max_command_buffer_empty_duration = max_planner_buffer_empty_duration = 0;
710
+  }
711
+
712
+  void GCodeQueue::auto_report_buffer_statistics() {
713
+    // Bit of a hack to try to catch planner buffer underruns without having logic
714
+    // running inside Stepper::block_phase_isr
715
+    const millis_t ms = millis();
716
+    if (planner.movesplanned() == 0) {
717
+      if (!planner_buffer_empty) { // the planner buffer wasn't empty, but now it is
718
+        planner_buffer_empty = true;
719
+        planner_buffer_underruns++;
720
+        planner_buffer_empty_at = ms;
721
+      }
722
+    }
723
+    else if (planner_buffer_empty) { // the planner buffer was empty, but now it's not
724
+      planner_buffer_empty = false;
725
+      const millis_t planner_buffer_empty_duration = ms - planner_buffer_empty_at;
726
+      NOLESS(max_planner_buffer_empty_duration, planner_buffer_empty_duration); // if it's longer than the currently tracked max duration, replace it
727
+    }
728
+
729
+    if (queue.auto_buffer_report_interval && ELAPSED(ms, queue.next_buffer_report_ms)) {
730
+      queue.next_buffer_report_ms = ms + 1000UL * queue.auto_buffer_report_interval;
731
+      PORT_REDIRECT(SERIAL_BOTH);
732
+      report_buffer_statistics();
733
+      PORT_RESTORE();
734
+    }
735
+  }
736
+
737
+#endif // BUFFER_MONITORING

+ 40
- 0
Marlin/src/gcode/queue.h Visa fil

@@ -197,6 +197,46 @@ public:
197 197
    */
198 198
   static inline void set_current_line_number(long n) { serial_state[ring_buffer.command_port().index].last_N = n; }
199 199
 
200
+  #if ENABLED(BUFFER_MONITORING)
201
+
202
+    private:
203
+
204
+    /**
205
+     * Track buffer underruns
206
+     */
207
+    static uint32_t command_buffer_underruns, planner_buffer_underruns;
208
+    static bool command_buffer_empty, planner_buffer_empty;
209
+    static millis_t max_command_buffer_empty_duration, max_planner_buffer_empty_duration,
210
+                    command_buffer_empty_at, planner_buffer_empty_at;
211
+
212
+    /**
213
+     * Report buffer statistics to the host to be able to detect buffer underruns
214
+     *
215
+     * Returns "D576 " followed by:
216
+     *  P<uint>   Planner space remaining
217
+     *  B<uint>   Command buffer space remaining
218
+     *  PU<uint>  Number of planner buffer underruns since last report
219
+     *  PD<uint>  Max time in ms the planner buffer was empty since last report
220
+     *  BU<uint>  Number of command buffer underruns since last report
221
+     *  BD<uint>  Max time in ms the command buffer was empty since last report
222
+     */
223
+    static void report_buffer_statistics();
224
+
225
+    static uint8_t auto_buffer_report_interval;
226
+    static millis_t next_buffer_report_ms;
227
+
228
+    public:
229
+
230
+    static void auto_report_buffer_statistics();
231
+
232
+    static inline void set_auto_report_interval(uint8_t v) {
233
+      NOMORE(v, 60);
234
+      auto_buffer_report_interval = v;
235
+      next_buffer_report_ms = millis() + 1000UL * v;
236
+    }
237
+
238
+  #endif // BUFFER_MONITORING
239
+
200 240
 private:
201 241
 
202 242
   static void get_serial_commands();

+ 1
- 1
buildroot/tests/STM32F103RET6_creality Visa fil

@@ -10,7 +10,7 @@ set -e
10 10
 # Build with configs included in the PR
11 11
 #
12 12
 use_example_configs "Creality/Ender-3 V2"
13
-opt_enable MARLIN_DEV_MODE
13
+opt_enable MARLIN_DEV_MODE BUFFER_MONITORING
14 14
 exec_test $1 $2 "Ender 3 v2" "$3"
15 15
 
16 16
 use_example_configs "Creality/Ender-3 V2"

Laddar…
Avbryt
Spara