Browse Source

Fix and improve G-code queue (#21122)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
X-Ryl669 3 years ago
parent
commit
ec42be346d
No account linked to committer's email address

+ 6
- 0
Marlin/Configuration_adv.h View File

@@ -2016,6 +2016,12 @@
2016 2016
   //#define SERIAL_STATS_DROPPED_RX
2017 2017
 #endif
2018 2018
 
2019
+// Monitor RX buffer usage
2020
+// Dump an error to the serial port if the serial receive buffer overflows.
2021
+// If you see these errors, increase the RX_BUFFER_SIZE value.
2022
+// Not supported on all platforms.
2023
+//#define RX_BUFFER_MONITOR
2024
+
2019 2025
 /**
2020 2026
  * Emergency Command Parser
2021 2027
  *

+ 1
- 1
Marlin/src/MarlinCore.cpp View File

@@ -407,7 +407,7 @@ void startOrResumeJob() {
407 407
  */
408 408
 inline void manage_inactivity(const bool ignore_stepper_queue=false) {
409 409
 
410
-  if (queue.length < BUFSIZE) queue.get_available_commands();
410
+  queue.get_available_commands();
411 411
 
412 412
   const millis_t ms = millis();
413 413
 

+ 37
- 0
Marlin/src/core/bug_on.h View File

@@ -0,0 +1,37 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Copyright (c) 2021 X-Ryl669 [https://blog.cyril.by]
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License
18
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
+ *
20
+ */
21
+#pragma once
22
+
23
+// We need SERIAL_ECHOPAIR and macros.h
24
+#include "serial.h"
25
+
26
+#if ENABLED(POSTMORTEM_DEBUGGING)
27
+  // Useful macro for stopping the CPU on an unexpected condition
28
+  // This is used like SERIAL_ECHOPAIR, that is: a key-value call of the local variables you want
29
+  // to dump to the serial port before stopping the CPU.
30
+  #define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": "); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0)
31
+#elif ENABLED(MARLIN_DEV_MODE)
32
+  // Don't stop the CPU here, but at least dump the bug on the serial port
33
+  #define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": BUG!\n"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0)
34
+#else
35
+  // Release mode, let's ignore the bug
36
+  #define BUG_ON(V...) NOOP
37
+#endif

+ 1
- 0
Marlin/src/core/language.h View File

@@ -131,6 +131,7 @@
131 131
 #define STR_WATCHDOG_FIRED                  "Watchdog timeout. Reset required."
132 132
 #define STR_ERR_KILLED                      "Printer halted. kill() called!"
133 133
 #define STR_ERR_STOPPED                     "Printer stopped due to errors. Fix the error and use M999 to restart. (Temperature is reset. Set it after restarting)"
134
+#define STR_ERR_SERIAL_MISMATCH             "Serial status mismatch"
134 135
 #define STR_BUSY_PROCESSING                 "busy: processing"
135 136
 #define STR_BUSY_PAUSED_FOR_USER            "busy: paused for user"
136 137
 #define STR_BUSY_PAUSED_FOR_INPUT           "busy: paused for input"

+ 25
- 0
Marlin/src/core/macros.h View File

@@ -349,6 +349,31 @@
349 349
   #define CALL_IF_EXISTS(Return, That, Method, ...) \
350 350
     static_cast<Return>(Private::Call_ ## Method(That, ##__VA_ARGS__))
351 351
 
352
+  // Compile-time string manipulation
353
+  namespace CompileTimeString {
354
+    // Simple compile-time parser to find the position of the end of a string
355
+    constexpr const char* findStringEnd(const char *str) {
356
+      return *str ? findStringEnd(str + 1) : str;
357
+    }
358
+
359
+    // Check whether a string contains a slash
360
+    constexpr bool containsSlash(const char *str) {
361
+      return *str == '/' ? true : (*str ? containsSlash(str + 1) : false);
362
+    }
363
+    // Find the last position of the slash
364
+    constexpr const char* findLastSlashPos(const char* str) {
365
+      return *str == '/' ? (str + 1) : findLastSlashPos(str - 1);
366
+    }
367
+    // Compile-time evaluation of the last part of a file path
368
+    // Typically used to shorten the path to file in compiled strings
369
+    // CompileTimeString::baseName(__FILE__) returns "macros.h" and not /path/to/Marlin/src/core/macros.h
370
+    constexpr const char* baseName(const char* str) {
371
+      return containsSlash(str) ? findLastSlashPos(findStringEnd(str)) : str;
372
+    }
373
+  }
374
+
375
+  #define ONLY_FILENAME CompileTimeString::baseName(__FILE__)
376
+
352 377
 #else
353 378
 
354 379
   #define MIN_2(a,b)      ((a)<(b)?(a):(b))

+ 4
- 0
Marlin/src/core/serial.cpp View File

@@ -52,6 +52,10 @@ PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMST
52 52
   #endif
53 53
 #endif
54 54
 
55
+#if ENABLED(MEATPACK)
56
+  MeatpackSerial<decltype(_SERIAL_IMPL)> mpSerial(false, _SERIAL_IMPL);
57
+#endif
58
+
55 59
 void serialprintPGM(PGM_P str) {
56 60
   while (const char c = pgm_read_byte(str++)) SERIAL_CHAR(c);
57 61
 }

+ 14
- 3
Marlin/src/core/serial.h View File

@@ -24,6 +24,10 @@
24 24
 #include "../inc/MarlinConfig.h"
25 25
 #include "serial_hook.h"
26 26
 
27
+#if ENABLED(MEATPACK)
28
+  #include "../feature/meatpack.h"
29
+#endif
30
+
27 31
 // Commonly-used strings in serial output
28 32
 extern const char NUL_STR[], SP_P_STR[], SP_T_STR[],
29 33
                   X_STR[], Y_STR[], Z_STR[], E_STR[],
@@ -69,12 +73,19 @@ extern uint8_t marlin_debug_flags;
69 73
     typedef MultiSerial<decltype(MYSERIAL0), TERN(HAS_ETHERNET, ConditionalSerial<decltype(MYSERIAL1)>, decltype(MYSERIAL1)), 0>      SerialOutputT;
70 74
   #endif
71 75
   extern SerialOutputT          multiSerial;
72
-  #define SERIAL_IMPL           multiSerial
76
+  #define _SERIAL_IMPL          multiSerial
73 77
 #else
74 78
   #define _PORT_REDIRECT(n,p)   NOOP
75 79
   #define _PORT_RESTORE(n)      NOOP
76 80
   #define SERIAL_ASSERT(P)      NOOP
77
-  #define SERIAL_IMPL           MYSERIAL0
81
+  #define _SERIAL_IMPL          MYSERIAL0
82
+#endif
83
+
84
+#if ENABLED(MEATPACK)
85
+  extern MeatpackSerial<decltype(_SERIAL_IMPL)> mpSerial;
86
+  #define SERIAL_IMPL          mpSerial
87
+#else
88
+  #define SERIAL_IMPL          _SERIAL_IMPL
78 89
 #endif
79 90
 
80 91
 #define SERIAL_OUT(WHAT, V...)  (void)SERIAL_IMPL.WHAT(V)
@@ -294,7 +305,7 @@ void serialprintPGM(PGM_P str);
294 305
 #endif
295 306
 
296 307
 #define SERIAL_ECHOPGM_P(P)         (serialprintPGM(P))
297
-#define SERIAL_ECHOLNPGM_P(P)       (serialprintPGM(P "\n"))
308
+#define SERIAL_ECHOLNPGM_P(P)       do{ serialprintPGM(P); SERIAL_EOL(); }while(0)
298 309
 
299 310
 #define SERIAL_ECHOPGM(S)           (serialprintPGM(PSTR(S)))
300 311
 #define SERIAL_ECHOLNPGM(S)         (serialprintPGM(PSTR(S "\n")))

+ 4
- 6
Marlin/src/feature/meatpack.cpp View File

@@ -110,7 +110,7 @@ void MeatPack::handle_rx_char_inner(const uint8_t c) {
110 110
   if (TEST(state, MPConfig_Bit_Active)) {                   // Is MeatPack active?
111 111
     if (!full_char_count) {                                 // No literal characters to fetch?
112 112
       uint8_t buf[2] = { 0, 0 };
113
-      register const uint8_t res = unpack_chars(c, buf);    // Decode the byte into one or two characters.
113
+      const uint8_t res = unpack_chars(c, buf);             // Decode the byte into one or two characters.
114 114
       if (res & kFirstCharIsLiteral) {                      // The 1st character couldn't be packed.
115 115
         ++full_char_count;                                  // So the next stream byte is a full character.
116 116
         if (res & kSecondCharIsLiteral) ++full_char_count;  // The 2nd character couldn't be packed. Another stream byte is a full character.
@@ -147,9 +147,7 @@ void MeatPack::handle_output_char(const uint8_t c) {
147 147
   #if ENABLED(MP_DEBUG)
148 148
     if (chars_decoded < 1024) {
149 149
       ++chars_decoded;
150
-      DEBUG_ECHOPGM("RB: ");
151
-      MYSERIAL.print((char)c);
152
-      DEBUG_EOL();
150
+      DEBUG_ECHOLNPAIR("RB: ", AS_CHAR(c));
153 151
     }
154 152
   #endif
155 153
 }
@@ -200,7 +198,7 @@ void MeatPack::handle_rx_char(const uint8_t c, const serial_index_t serial_ind)
200 198
   }
201 199
 
202 200
   if (cmd_is_next) {                      // Were two command bytes received?
203
-    PORT_REDIRECT(serial_ind);
201
+    PORT_REDIRECT(SERIAL_PORTMASK(serial_ind));
204 202
     handle_command((MeatPack_Command)c);  // Then the byte is a MeatPack command
205 203
     cmd_is_next = false;
206 204
     return;
@@ -219,7 +217,7 @@ uint8_t MeatPack::get_result_char(char* const __restrict out) {
219 217
   if (char_out_count) {
220 218
     res = char_out_count;
221 219
     char_out_count = 0;
222
-    for (register uint8_t i = 0; i < res; ++i)
220
+    for (uint8_t i = 0; i < res; ++i)
223 221
       out[i] = (char)char_out_buf[i];
224 222
   }
225 223
   return res;

+ 56
- 3
Marlin/src/feature/meatpack.h View File

@@ -49,6 +49,7 @@
49 49
 #pragma once
50 50
 
51 51
 #include <stdint.h>
52
+#include "../core/serial_hook.h"
52 53
 
53 54
 /**
54 55
  * Commands sent to MeatPack to control its behavior.
@@ -78,8 +79,6 @@ enum MeatPack_ConfigStateBits : uint8_t {
78 79
 };
79 80
 
80 81
 class MeatPack {
81
-private:
82
-  friend class GCodeQueue;
83 82
 
84 83
   // Utility definitions
85 84
   static const uint8_t kCommandByte         = 0b11111111,
@@ -99,6 +98,7 @@ private:
99 98
                  char_out_count;  // Stores number of characters to be read out.
100 99
   static uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters
101 100
 
101
+public:
102 102
   // Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences,
103 103
   // and will control state internally.
104 104
   static void handle_rx_char(const uint8_t c, const serial_index_t serial_ind);
@@ -113,7 +113,6 @@ private:
113 113
 
114 114
   static void reset_state();
115 115
   static void report_state();
116
-  static uint8_t unpacked_char(register const uint8_t in);
117 116
   static uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out);
118 117
   static void handle_command(const MeatPack_Command c);
119 118
   static void handle_output_char(const uint8_t c);
@@ -121,3 +120,57 @@ private:
121 120
 };
122 121
 
123 122
 extern MeatPack meatpack;
123
+
124
+// Implement the MeatPack serial class so it's transparent to rest of the code
125
+template <typename SerialT>
126
+struct MeatpackSerial : public SerialBase <MeatpackSerial < SerialT >> {
127
+  typedef SerialBase< MeatpackSerial<SerialT> > BaseClassT;
128
+
129
+  SerialT & out;
130
+
131
+  char serialBuffer[2];
132
+  uint8_t charCount;
133
+  uint8_t readIndex;
134
+
135
+  NO_INLINE size_t write(uint8_t c) { return out.write(c); }
136
+  void flush()                      { out.flush();  }
137
+  void begin(long br)               { out.begin(br); readIndex = 0; }
138
+  void end()                        { out.end(); }
139
+
140
+  void msgDone()                    { out.msgDone(); }
141
+  // Existing instances implement Arduino's operator bool, so use that if it's available
142
+  bool connected()                  { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
143
+  void flushTX()                    { CALL_IF_EXISTS(void, &out, flushTX); }
144
+
145
+  int available(uint8_t index) {
146
+    // There is a potential issue here with multiserial, since it'll return its decoded buffer whatever the serial index here.
147
+    // So, instead of doing MeatpackSerial<MultiSerial<...>> we should do MultiSerial<MeatpackSerial<...>, MeatpackSerial<...>>
148
+    // TODO, let's fix this later on
149
+
150
+    if (charCount) return charCount;          // The buffer still has data
151
+    if (out.available(index) <= 0) return 0;  // No data to read
152
+
153
+    // Don't read in read method, instead do it here, so we can make progress in the read method
154
+    const int r = out.read(index);
155
+    if (r == -1) return 0;  // This is an error from the underlying serial code
156
+    meatpack.handle_rx_char((uint8_t)r, index);
157
+    charCount = meatpack.get_result_char(serialBuffer);
158
+    readIndex = 0;
159
+
160
+    return charCount;
161
+  }
162
+
163
+  int readImpl(const uint8_t index) {
164
+    // Not enough char to make progress?
165
+    if (charCount == 0 && available(index) == 0) return -1;
166
+
167
+    charCount--;
168
+    return serialBuffer[readIndex++];
169
+  }
170
+
171
+  int read(uint8_t index) { return readImpl(index); }
172
+  int available()         { return available(0); }
173
+  int read()              { return readImpl(0); }
174
+
175
+  MeatpackSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
176
+};

+ 36
- 42
Marlin/src/gcode/calibrate/M100.cpp View File

@@ -51,7 +51,7 @@
51 51
  * Also, there are two support functions that can be called from a developer's C code.
52 52
  *
53 53
  *    uint16_t check_for_free_memory_corruption(PGM_P const free_memory_start);
54
- *    void M100_dump_routine(PGM_P const title, const char * const start, const char * const end);
54
+ *    void M100_dump_routine(PGM_P const title, const char * const start, const uintptr_t size);
55 55
  *
56 56
  * Initial version by Roxy-3D
57 57
  */
@@ -151,7 +151,7 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
151 151
    *  the block. If so, it may indicate memory corruption due to a bad pointer.
152 152
    *  Unexpected bytes are flagged in the right column.
153 153
    */
154
-  inline void dump_free_memory(char *start_free_memory, char *end_free_memory) {
154
+  void dump_free_memory(char *start_free_memory, char *end_free_memory) {
155 155
     //
156 156
     // Start and end the dump on a nice 16 byte boundary
157 157
     // (even though the values are not 16-byte aligned).
@@ -182,12 +182,12 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
182 182
     }
183 183
   }
184 184
 
185
-  void M100_dump_routine(PGM_P const title, const char * const start, const char * const end) {
186
-    serialprintPGM(title);
187
-    SERIAL_EOL();
185
+  void M100_dump_routine(PGM_P const title, const char * const start, const uintptr_t size) {
186
+    SERIAL_ECHOLNPGM_P(title);
188 187
     //
189 188
     // Round the start and end locations to produce full lines of output
190 189
     //
190
+    const char * const end = start + size - 1;
191 191
     dump_free_memory(
192 192
       (char*)(uintptr_t(uint32_t(start) & ~0xFUL)), // Align to 16-byte boundary
193 193
       (char*)(uintptr_t(uint32_t(end)   |  0xFUL))  // Align end_free_memory to the 15th byte (at or above end_free_memory)
@@ -197,27 +197,27 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
197 197
 #endif // M100_FREE_MEMORY_DUMPER
198 198
 
199 199
 inline int check_for_free_memory_corruption(PGM_P const title) {
200
-  serialprintPGM(title);
200
+  SERIAL_ECHOPGM_P(title);
201 201
 
202 202
   char *start_free_memory = free_memory_start, *end_free_memory = free_memory_end;
203 203
   int n = end_free_memory - start_free_memory;
204 204
 
205
-  SERIAL_ECHOPAIR("\nfmc() n=", n);
206
-  SERIAL_ECHOPAIR("\nfree_memory_start=", hex_address(free_memory_start));
207
-  SERIAL_ECHOLNPAIR("  end_free_memory=", hex_address(end_free_memory));
205
+  SERIAL_ECHOLNPAIR("\nfmc() n=", n,
206
+                    "\nfree_memory_start=", hex_address(free_memory_start),
207
+                    "  end=", hex_address(end_free_memory));
208 208
 
209 209
   if (end_free_memory < start_free_memory)  {
210 210
     SERIAL_ECHOPGM(" end_free_memory < Heap ");
211
-    // SET_INPUT_PULLUP(63);           // if the developer has a switch wired up to their controller board
212
-    // safe_delay(5);                  // this code can be enabled to pause the display as soon as the
213
-    // while ( READ(63))               // malfunction is detected.   It is currently defaulting to a switch
214
-    //   idle();                       // being on pin-63 which is unassigend and available on most controller
215
-    // safe_delay(20);                 // boards.
216
-    // while ( !READ(63))
217
-    //   idle();
211
+    //SET_INPUT_PULLUP(63);           // if the developer has a switch wired up to their controller board
212
+    //safe_delay(5);                  // this code can be enabled to pause the display as soon as the
213
+    //while ( READ(63))               // malfunction is detected.   It is currently defaulting to a switch
214
+    //  idle();                       // being on pin-63 which is unassigend and available on most controller
215
+    //safe_delay(20);                 // boards.
216
+    //while ( !READ(63))
217
+    //  idle();
218 218
     serial_delay(20);
219 219
     #if ENABLED(M100_FREE_MEMORY_DUMPER)
220
-      M100_dump_routine(PSTR("   Memory corruption detected with end_free_memory<Heap\n"), (const char*)0x1B80, (const char*)0x21FF);
220
+      M100_dump_routine(PSTR("   Memory corruption detected with end_free_memory<Heap\n"), (const char*)0x1B80, 0x0680);
221 221
     #endif
222 222
   }
223 223
 
@@ -227,13 +227,11 @@ inline int check_for_free_memory_corruption(PGM_P const title) {
227 227
     if (start_free_memory[i] == TEST_BYTE) {
228 228
       int32_t j = count_test_bytes(start_free_memory + i);
229 229
       if (j > 8) {
230
-        // SERIAL_ECHOPAIR("Found ", j);
231
-        // SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(start_free_memory + i));
230
+        //SERIAL_ECHOPAIR("Found ", j);
231
+        //SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(start_free_memory + i));
232 232
         i += j;
233 233
         block_cnt++;
234
-        SERIAL_ECHOPAIR(" (", block_cnt);
235
-        SERIAL_ECHOPAIR(") found=", j);
236
-        SERIAL_ECHOLNPGM("   ");
234
+        SERIAL_ECHOLNPAIR(" (", block_cnt, ") found=", j);
237 235
       }
238 236
     }
239 237
   }
@@ -269,8 +267,7 @@ inline void free_memory_pool_report(char * const start_free_memory, const int32_
269 267
     if (*addr == TEST_BYTE) {
270 268
       const int32_t j = count_test_bytes(addr);
271 269
       if (j > 8) {
272
-        SERIAL_ECHOPAIR("Found ", j);
273
-        SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(addr));
270
+        SERIAL_ECHOLNPAIR("Found ", j, " bytes free at ", hex_address(addr));
274 271
         if (j > max_cnt) {
275 272
           max_cnt  = j;
276 273
           max_addr = addr;
@@ -280,11 +277,10 @@ inline void free_memory_pool_report(char * const start_free_memory, const int32_
280 277
       }
281 278
     }
282 279
   }
283
-  if (block_cnt > 1) {
284
-    SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area.");
285
-    SERIAL_ECHOPAIR("\nLargest free block is ", max_cnt);
286
-    SERIAL_ECHOLNPAIR(" bytes at ", hex_address(max_addr));
287
-  }
280
+  if (block_cnt > 1) SERIAL_ECHOLNPAIR(
281
+    "\nMemory Corruption detected in free memory area."
282
+    "\nLargest free block is ", max_cnt, " bytes at ", hex_address(max_addr)
283
+  );
288 284
   SERIAL_ECHOLNPAIR("check_for_free_memory_corruption() = ", check_for_free_memory_corruption(PSTR("M100 F ")));
289 285
 }
290 286
 
@@ -294,12 +290,12 @@ inline void free_memory_pool_report(char * const start_free_memory, const int32_
294 290
    *  Corrupt <num> locations in the free memory pool and report the corrupt addresses.
295 291
    *  This is useful to check the correctness of the M100 D and the M100 F commands.
296 292
    */
297
-  inline void corrupt_free_memory(char *start_free_memory, const uint32_t size) {
293
+  inline void corrupt_free_memory(char *start_free_memory, const uintptr_t size) {
298 294
     start_free_memory += 8;
299 295
     const uint32_t near_top = top_of_stack() - start_free_memory - 250, // -250 to avoid interrupt activity that's altered the stack.
300 296
                    j = near_top / (size + 1);
301 297
 
302
-    SERIAL_ECHOLNPGM("Corrupting free memory block.\n");
298
+    SERIAL_ECHOLNPGM("Corrupting free memory block.");
303 299
     for (uint32_t i = 1; i <= size; i++) {
304 300
       char * const addr = start_free_memory + i * j;
305 301
       *addr = i;
@@ -322,8 +318,8 @@ inline void init_free_memory(char *start_free_memory, int32_t size) {
322 318
     return;
323 319
   }
324 320
 
325
-  start_free_memory += 8;       // move a few bytes away from the heap just because we don't want
326
-                  // to be altering memory that close to it.
321
+  start_free_memory += 8; // move a few bytes away from the heap just because we
322
+                          // don't want to be altering memory that close to it.
327 323
   memset(start_free_memory, TEST_BYTE, size);
328 324
 
329 325
   SERIAL_ECHO(size);
@@ -342,16 +338,16 @@ inline void init_free_memory(char *start_free_memory, int32_t size) {
342 338
  * M100: Free Memory Check
343 339
  */
344 340
 void GcodeSuite::M100() {
345
-
346 341
   char *sp = top_of_stack();
347 342
   if (!free_memory_end) free_memory_end = sp - MEMORY_END_CORRECTION;
348
-  SERIAL_ECHOPAIR("\nbss_end               : ", hex_address(end_bss));
349
-  if (heaplimit) SERIAL_ECHOPAIR("\n__heaplimit           : ", hex_address(heaplimit));
350
-  SERIAL_ECHOPAIR("\nfree_memory_start     : ", hex_address(free_memory_start));
343
+                  SERIAL_ECHOPAIR("\nbss_end               : ", hex_address(end_bss));
344
+  if (heaplimit)  SERIAL_ECHOPAIR("\n__heaplimit           : ", hex_address(heaplimit));
345
+                  SERIAL_ECHOPAIR("\nfree_memory_start     : ", hex_address(free_memory_start));
351 346
   if (stacklimit) SERIAL_ECHOPAIR("\n__stacklimit          : ", hex_address(stacklimit));
352
-  SERIAL_ECHOPAIR("\nfree_memory_end       : ", hex_address(free_memory_end));
353
-  if (MEMORY_END_CORRECTION)  SERIAL_ECHOPAIR("\nMEMORY_END_CORRECTION: ", MEMORY_END_CORRECTION);
354
-  SERIAL_ECHOLNPAIR("\nStack Pointer         : ", hex_address(sp));
347
+                  SERIAL_ECHOPAIR("\nfree_memory_end       : ", hex_address(free_memory_end));
348
+  if (MEMORY_END_CORRECTION)
349
+                  SERIAL_ECHOPAIR("\nMEMORY_END_CORRECTION : ", MEMORY_END_CORRECTION);
350
+                  SERIAL_ECHOLNPAIR("\nStack Pointer       : ", hex_address(sp));
355 351
 
356 352
   // Always init on the first invocation of M100
357 353
   static bool m100_not_initialized = true;
@@ -369,10 +365,8 @@ void GcodeSuite::M100() {
369 365
     return free_memory_pool_report(free_memory_start, free_memory_end - free_memory_start);
370 366
 
371 367
   #if ENABLED(M100_FREE_MEMORY_CORRUPTOR)
372
-
373 368
     if (parser.seen('C'))
374 369
       return corrupt_free_memory(free_memory_start, parser.value_int());
375
-
376 370
   #endif
377 371
 }
378 372
 

+ 11
- 16
Marlin/src/gcode/gcode.cpp View File

@@ -260,13 +260,6 @@ void GcodeSuite::dwell(millis_t time) {
260 260
 
261 261
 #endif // HAS_LEVELING && G29_RETRY_AND_RECOVER
262 262
 
263
-//
264
-// Placeholders for non-migrated codes
265
-//
266
-#if ENABLED(M100_FREE_MEMORY_WATCHER)
267
-  extern void M100_dump_routine(PGM_P const title, const char * const start, const char * const end);
268
-#endif
269
-
270 263
 /**
271 264
  * Process the parsed command and dispatch it to its handler
272 265
  */
@@ -994,30 +987,32 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
994 987
   SERIAL_OUT(msgDone); // Call the msgDone serial hook to signal command processing done
995 988
 }
996 989
 
990
+#if ENABLED(M100_FREE_MEMORY_DUMPER)
991
+  void M100_dump_routine(PGM_P const title, const char * const start, const uintptr_t size);
992
+#endif
993
+
997 994
 /**
998 995
  * Process a single command and dispatch it to its handler
999 996
  * This is called from the main loop()
1000 997
  */
1001 998
 void GcodeSuite::process_next_command() {
1002
-  char * const current_command = queue.command_buffer[queue.index_r];
999
+  GCodeQueue::CommandLine &command = queue.ring_buffer.peek_next_command();
1003 1000
 
1004
-  PORT_REDIRECT(SERIAL_PORTMASK(queue.port[queue.index_r]));
1001
+  PORT_REDIRECT(SERIAL_PORTMASK(command.port));
1005 1002
 
1006
-  #if ENABLED(POWER_LOSS_RECOVERY)
1007
-    recovery.queue_index_r = queue.index_r;
1008
-  #endif
1003
+  TERN_(POWER_LOSS_RECOVERY, recovery.queue_index_r = queue.ring_buffer.index_r);
1009 1004
 
1010 1005
   if (DEBUGGING(ECHO)) {
1011 1006
     SERIAL_ECHO_START();
1012
-    SERIAL_ECHOLN(current_command);
1007
+    SERIAL_ECHOLN(command.buffer);
1013 1008
     #if ENABLED(M100_FREE_MEMORY_DUMPER)
1014
-      SERIAL_ECHOPAIR("slot:", queue.index_r);
1015
-      M100_dump_routine(PSTR("   Command Queue:"), &queue.command_buffer[0][0], &queue.command_buffer[BUFSIZE - 1][MAX_CMD_SIZE - 1]);
1009
+      SERIAL_ECHOPAIR("slot:", queue.ring_buffer.index_r);
1010
+      M100_dump_routine(PSTR("   Command Queue:"), (const char*)&queue.ring_buffer, sizeof(queue.ring_buffer));
1016 1011
     #endif
1017 1012
   }
1018 1013
 
1019 1014
   // Parse the next command in the queue
1020
-  parser.parse(current_command);
1015
+  parser.parse(command.buffer);
1021 1016
   process_parsed_command();
1022 1017
 }
1023 1018
 

+ 1
- 1
Marlin/src/gcode/host/M110.cpp View File

@@ -29,6 +29,6 @@
29 29
 void GcodeSuite::M110() {
30 30
 
31 31
   if (parser.seenval('N'))
32
-    queue.last_N[queue.command_port()] = parser.value_long();
32
+    queue.set_current_line_number(parser.value_long());
33 33
 
34 34
 }

+ 1
- 3
Marlin/src/gcode/host/M118.cpp View File

@@ -52,9 +52,7 @@ void GcodeSuite::M118() {
52 52
     while (*p == ' ') ++p;
53 53
   }
54 54
 
55
-  #if HAS_MULTI_SERIAL
56
-    PORT_REDIRECT(WITHIN(port, 0, NUM_SERIAL) ? (port ? _BV(port - 1) : SERIAL_ALL) : multiSerial.portMask);
57
-  #endif
55
+  PORT_REDIRECT(WITHIN(port, 0, NUM_SERIAL) ? (port ? SERIAL_PORTMASK(port - 1) : SERIAL_ALL) : multiSerial.portMask);
58 56
 
59 57
   if (hasE) SERIAL_ECHO_START();
60 58
   if (hasA) SERIAL_ECHOPGM("//");

+ 164
- 190
Marlin/src/gcode/queue.cpp View File

@@ -35,6 +35,7 @@ GCodeQueue queue;
35 35
 #include "../module/planner.h"
36 36
 #include "../module/temperature.h"
37 37
 #include "../MarlinCore.h"
38
+#include "../core/bug_on.h"
38 39
 
39 40
 #if ENABLED(PRINTER_EVENT_LEDS)
40 41
   #include "../feature/leds/printer_event_leds.h"
@@ -48,10 +49,6 @@ GCodeQueue queue;
48 49
   #include "../feature/binary_stream.h"
49 50
 #endif
50 51
 
51
-#if ENABLED(MEATPACK)
52
-  #include "../feature/meatpack.h"
53
-#endif
54
-
55 52
 #if ENABLED(POWER_LOSS_RECOVERY)
56 53
   #include "../feature/powerloss.h"
57 54
 #endif
@@ -67,44 +64,17 @@ PGMSTR(G28_STR, "G28");
67 64
   static millis_t last_command_time = 0;
68 65
 #endif
69 66
 
70
-/**
71
- * GCode line number handling. Hosts may opt to include line numbers when
72
- * sending commands to Marlin, and lines will be checked for sequentiality.
73
- * M110 N<int> sets the current line number.
74
- */
75
-long GCodeQueue::last_N[NUM_SERIAL];
76
-
77
-/**
78
- * GCode Command Queue
79
- * A simple ring buffer of BUFSIZE command strings.
80
- *
81
- * Commands are copied into this buffer by the command injectors
82
- * (immediate, serial, sd card) and they are processed sequentially by
83
- * the main loop. The gcode.process_next_command method parses the next
84
- * command and hands off execution to individual handler functions.
85
- */
86
-uint8_t GCodeQueue::length = 0,  // Count of commands in the queue
87
-        GCodeQueue::index_r = 0, // Ring buffer read position
88
-        GCodeQueue::index_w = 0; // Ring buffer write position
89
-
90
-char GCodeQueue::command_buffer[BUFSIZE][MAX_CMD_SIZE];
67
+GCodeQueue::SerialState GCodeQueue::serial_state[NUM_SERIAL] = { 0 };
68
+GCodeQueue::RingBuffer GCodeQueue::ring_buffer = { 0 };
91 69
 
92
-/*
93
- * The port that the command was received on
94
- */
95
-#if HAS_MULTI_SERIAL
96
-  serial_index_t GCodeQueue::port[BUFSIZE];
70
+#if NO_TIMEOUTS > 0
71
+  static millis_t last_command_time = 0;
97 72
 #endif
98 73
 
99 74
 /**
100 75
  * Serial command injection
101 76
  */
102 77
 
103
-// Number of characters read in the current line of serial input
104
-static int serial_count[NUM_SERIAL] = { 0 };
105
-
106
-bool send_ok[BUFSIZE];
107
-
108 78
 /**
109 79
  * Next Injected PROGMEM Command pointer. (nullptr == empty)
110 80
  * Internal commands are enqueued ahead of serial / SD commands.
@@ -116,38 +86,16 @@ PGM_P GCodeQueue::injected_commands_P; // = nullptr
116 86
  */
117 87
 char GCodeQueue::injected_commands[64]; // = { 0 }
118 88
 
119
-GCodeQueue::GCodeQueue() {
120
-  // Send "ok" after commands by default
121
-  LOOP_L_N(i, COUNT(send_ok)) send_ok[i] = true;
122
-}
123
-
124
-/**
125
- * Check whether there are any commands yet to be executed
126
- */
127
-bool GCodeQueue::has_commands_queued() {
128
-  return queue.length || injected_commands_P || injected_commands[0];
129
-}
130
-
131
-/**
132
- * Clear the Marlin command queue
133
- */
134
-void GCodeQueue::clear() {
135
-  index_r = index_w = length = 0;
136
-}
137 89
 
138
-/**
139
- * Once a new command is in the ring buffer, call this to commit it
140
- */
141
-void GCodeQueue::_commit_command(bool say_ok
90
+void GCodeQueue::RingBuffer::commit_command(bool skip_ok
142 91
   #if HAS_MULTI_SERIAL
143 92
     , serial_index_t serial_ind/*=-1*/
144 93
   #endif
145 94
 ) {
146
-  send_ok[index_w] = say_ok;
147
-  TERN_(HAS_MULTI_SERIAL, port[index_w] = serial_ind);
95
+  commands[index_w].skip_ok = skip_ok;
96
+  TERN_(HAS_MULTI_SERIAL, commands[index_w].port = serial_ind);
148 97
   TERN_(POWER_LOSS_RECOVERY, recovery.commit_sdpos(index_w));
149
-  if (++index_w >= BUFSIZE) index_w = 0;
150
-  length++;
98
+  advance_pos(index_w, 1);
151 99
 }
152 100
 
153 101
 /**
@@ -155,14 +103,14 @@ void GCodeQueue::_commit_command(bool say_ok
155 103
  * Return true if the command was successfully added.
156 104
  * Return false for a full buffer, or if the 'command' is a comment.
157 105
  */
158
-bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
106
+bool GCodeQueue::RingBuffer::enqueue(const char* cmd, bool skip_ok/*=true*/
159 107
   #if HAS_MULTI_SERIAL
160 108
     , serial_index_t serial_ind/*=-1*/
161 109
   #endif
162 110
 ) {
163 111
   if (*cmd == ';' || length >= BUFSIZE) return false;
164
-  strcpy(command_buffer[index_w], cmd);
165
-  _commit_command(say_ok
112
+  strcpy(commands[index_w].buffer, cmd);
113
+  commit_command(skip_ok
166 114
     #if HAS_MULTI_SERIAL
167 115
       , serial_ind
168 116
     #endif
@@ -175,14 +123,11 @@ bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
175 123
  * Return true if the command was consumed
176 124
  */
177 125
 bool GCodeQueue::enqueue_one(const char* cmd) {
178
-
179
-  //SERIAL_ECHOPGM("enqueue_one(\"");
180
-  //SERIAL_ECHO(cmd);
181
-  //SERIAL_ECHOPGM("\") \n");
126
+  //SERIAL_ECHOLNPAIR("enqueue_one(\"", cmd, "\")");
182 127
 
183 128
   if (*cmd == 0 || ISEOL(*cmd)) return true;
184 129
 
185
-  if (_enqueue(cmd)) {
130
+  if (ring_buffer.enqueue(cmd)) {
186 131
     SERIAL_ECHO_MSG(STR_ENQUEUEING, cmd, "\"");
187 132
     return true;
188 133
   }
@@ -260,7 +205,7 @@ bool GCodeQueue::enqueue_one_P(PGM_P const pgcode) {
260 205
   char cmd[i + 1];
261 206
   memcpy_P(cmd, p, i);
262 207
   cmd[i] = '\0';
263
-  return _enqueue(cmd);
208
+  return ring_buffer.enqueue(cmd);
264 209
 }
265 210
 
266 211
 /**
@@ -291,20 +236,21 @@ void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
291 236
  *   P<int>  Planner space remaining
292 237
  *   B<int>  Block queue space remaining
293 238
  */
294
-void GCodeQueue::ok_to_send() {
239
+void GCodeQueue::RingBuffer::ok_to_send() {
295 240
   #if NO_TIMEOUTS > 0
296 241
     // Start counting from the last command's execution
297 242
     last_command_time = millis();
298 243
   #endif
244
+  CommandLine &command = commands[index_r];
299 245
   #if HAS_MULTI_SERIAL
300
-    const serial_index_t serial_ind = command_port();
246
+    const serial_index_t serial_ind = command.port;
301 247
     if (serial_ind < 0) return;
302 248
     PORT_REDIRECT(SERIAL_PORTMASK(serial_ind));   // Reply to the serial port that sent the command
303 249
   #endif
304
-  if (!send_ok[index_r]) return;
250
+  if (command.skip_ok) return;
305 251
   SERIAL_ECHOPGM(STR_OK);
306 252
   #if ENABLED(ADVANCED_OK)
307
-    char* p = command_buffer[index_r];
253
+    char* p = command.buffer;
308 254
     if (*p == 'N') {
309 255
       SERIAL_CHAR(' ', *p++);
310 256
       while (NUMERIC_SIGNED(*p))
@@ -321,27 +267,40 @@ void GCodeQueue::ok_to_send() {
321 267
  * indicate that a command needs to be re-sent.
322 268
  */
323 269
 void GCodeQueue::flush_and_request_resend() {
324
-  const serial_index_t serial_ind = command_port();
270
+  const serial_index_t serial_ind = ring_buffer.command_port();
325 271
   #if HAS_MULTI_SERIAL
326 272
     if (serial_ind < 0) return;                   // Never mind. Command came from SD or Flash Drive
327 273
     PORT_REDIRECT(SERIAL_PORTMASK(serial_ind));   // Reply to the serial port that sent the command
328 274
   #endif
329 275
   SERIAL_FLUSH();
330 276
   SERIAL_ECHOPGM(STR_RESEND);
331
-  SERIAL_ECHOLN(last_N[serial_ind] + 1);
332
-  ok_to_send();
277
+  SERIAL_ECHOLN(serial_state[serial_ind].last_N + 1);
333 278
 }
334 279
 
335
-
336 280
 // Multiserial already handle the dispatch to/from multiple port by itself
337 281
 inline bool serial_data_available(uint8_t index = SERIAL_ALL) {
338 282
   if (index == SERIAL_ALL) {
339 283
     for (index = 0; index < NUM_SERIAL; index++) {
340
-      if (SERIAL_IMPL.available(index) > 0) return true;
284
+      const int a = SERIAL_IMPL.available(index);
285
+      #if BOTH(RX_BUFFER_MONITOR, RX_BUFFER_SIZE)
286
+        if (a > RX_BUFFER_SIZE - 2) {
287
+          PORT_REDIRECT(SERIAL_PORTMASK(index));
288
+          SERIAL_ERROR_MSG("RX BUF overflow, increase RX_BUFFER_SIZE: ", a);
289
+        }
290
+      #endif
291
+      if (a > 0) return true;
341 292
     }
342 293
     return false;
343 294
   }
344
-  return SERIAL_IMPL.available(index) > 0;
295
+  const int a = SERIAL_IMPL.available(index);
296
+  #if BOTH(RX_BUFFER_MONITOR, RX_BUFFER_SIZE)
297
+    if (a > RX_BUFFER_SIZE - 2) {
298
+      PORT_REDIRECT(SERIAL_PORTMASK(index));
299
+      SERIAL_ERROR_MSG("RX BUF overflow, increase RX_BUFFER_SIZE: ", a);
300
+    }
301
+  #endif
302
+
303
+  return a > 0;
345 304
 }
346 305
 
347 306
 inline int read_serial(const uint8_t index) { return SERIAL_IMPL.read(index); }
@@ -349,11 +308,11 @@ inline int read_serial(const uint8_t index) { return SERIAL_IMPL.read(index); }
349 308
 void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
350 309
   PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
351 310
   SERIAL_ERROR_START();
352
-  serialprintPGM(err);
353
-  SERIAL_ECHOLN(last_N[serial_ind]);
354
-  while (read_serial(serial_ind) != -1);      // Clear out the RX buffer
311
+  SERIAL_ECHOPGM_P(err);
312
+  SERIAL_ECHOLN(serial_state[serial_ind].last_N);
313
+  while (read_serial(serial_ind) != -1) { /* nada */ } // Clear out the RX buffer. Why don't use flush here ?
355 314
   flush_and_request_resend();
356
-  serial_count[serial_ind] = 0;
315
+  serial_state[serial_ind].count = 0;
357 316
 }
358 317
 
359 318
 FORCE_INLINE bool is_M29(const char * const cmd) {  // matches "M29" & "M29 ", but not "M290", etc
@@ -440,10 +399,6 @@ inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind
440 399
  * left on the serial port.
441 400
  */
442 401
 void GCodeQueue::get_serial_commands() {
443
-  static char serial_line_buffer[NUM_SERIAL][MAX_CMD_SIZE];
444
-
445
-  static uint8_t serial_input_state[NUM_SERIAL] = { PS_NORMAL };
446
-
447 402
   #if ENABLED(BINARY_FILE_TRANSFER)
448 403
     if (card.flag.binary_mode) {
449 404
       /**
@@ -451,7 +406,7 @@ void GCodeQueue::get_serial_commands() {
451 406
        * receive buffer (which limits the packet size to MAX_CMD_SIZE).
452 407
        * The receive buffer also limits the packet size for reliable transmission.
453 408
        */
454
-      binaryStream[card.transfer_port_index].receive(serial_line_buffer[card.transfer_port_index]);
409
+      binaryStream[card.transfer_port_index].receive(serial_state[card.transfer_port_index].line_buffer);
455 410
       return;
456 411
     }
457 412
   #endif
@@ -460,122 +415,140 @@ void GCodeQueue::get_serial_commands() {
460 415
   // send "wait" to indicate Marlin is still waiting.
461 416
   #if NO_TIMEOUTS > 0
462 417
     const millis_t ms = millis();
463
-    if (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
418
+    if (ring_buffer.empty() && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
464 419
       SERIAL_ECHOLNPGM(STR_WAIT);
465 420
       last_command_time = ms;
466 421
     }
467 422
   #endif
468 423
 
469
-  /**
470
-   * Loop while serial characters are incoming and the queue is not full
471
-   */
472
-  while (length < BUFSIZE && serial_data_available()) {
424
+  // Loop while serial characters are incoming and the queue is not full
425
+  for (bool hadData = true; hadData;) {
426
+    // Unless a serial port has data, this will exit on next iteration
427
+    hadData = false;
428
+
473 429
     LOOP_L_N(p, NUM_SERIAL) {
430
+      // Check if the queue is full and exit if it is.
431
+      if (ring_buffer.full()) return;
474 432
 
475
-      const int c = read_serial(p);
476
-      if (c < 0) continue;
477
-
478
-      #if ENABLED(MEATPACK)
479
-        meatpack.handle_rx_char(uint8_t(c), p);
480
-        char c_res[2] = { 0, 0 };
481
-        const uint8_t char_count = meatpack.get_result_char(c_res);
482
-      #else
483
-        constexpr uint8_t char_count = 1;
484
-      #endif
433
+      // No data for this port ? Skip it
434
+      if (!serial_data_available(p)) continue;
485 435
 
486
-      LOOP_L_N(char_index, char_count) {
487
-        const char serial_char = TERN(MEATPACK, c_res[char_index], c);
436
+      // Ok, we have some data to process, let's make progress here
437
+      hadData = true;
488 438
 
489
-        if (ISEOL(serial_char)) {
439
+      const int c = read_serial(p);
440
+      if (c < 0) {
441
+        // This should never happen, let's log it
442
+        PORT_REDIRECT(SERIAL_PORTMASK(p));     // Reply to the serial port that sent the command
443
+        // Crash here to get more information why it failed
444
+        BUG_ON("SP available but read -1");
445
+        SERIAL_ERROR_MSG(STR_ERR_SERIAL_MISMATCH);
446
+        SERIAL_FLUSH();
447
+        continue;
448
+      }
490 449
 
491
-          // Reset our state, continue if the line was empty
492
-          if (process_line_done(serial_input_state[p], serial_line_buffer[p], serial_count[p]))
493
-            continue;
450
+      const char serial_char = (char)c;
451
+      SerialState &serial = serial_state[p];
494 452
 
495
-          char* command = serial_line_buffer[p];
453
+      if (ISEOL(serial_char)) {
496 454
 
497
-          while (*command == ' ') command++;                   // Skip leading spaces
498
-          char *npos = (*command == 'N') ? command : nullptr;  // Require the N parameter to start the line
455
+        // Reset our state, continue if the line was empty
456
+        if (process_line_done(serial.input_state, serial.line_buffer, serial.count))
457
+          continue;
499 458
 
500
-          if (npos) {
459
+        char* command = serial.line_buffer;
501 460
 
502
-            const bool M110 = !!strstr_P(command, PSTR("M110"));
461
+        while (*command == ' ') command++;                   // Skip leading spaces
462
+        char *npos = (*command == 'N') ? command : nullptr;  // Require the N parameter to start the line
503 463
 
504
-            if (M110) {
505
-              char* n2pos = strchr(command + 4, 'N');
506
-              if (n2pos) npos = n2pos;
507
-            }
464
+        if (npos) {
508 465
 
509
-            const long gcode_N = strtol(npos + 1, nullptr, 10);
466
+          const bool M110 = !!strstr_P(command, PSTR("M110"));
510 467
 
511
-            if (gcode_N != last_N[p] + 1 && !M110)
512
-              return gcode_line_error(PSTR(STR_ERR_LINE_NO), p);
468
+          if (M110) {
469
+            char* n2pos = strchr(command + 4, 'N');
470
+            if (n2pos) npos = n2pos;
471
+          }
513 472
 
514
-            char *apos = strrchr(command, '*');
515
-            if (apos) {
516
-              uint8_t checksum = 0, count = uint8_t(apos - command);
517
-              while (count) checksum ^= command[--count];
518
-              if (strtol(apos + 1, nullptr, 10) != checksum)
519
-                return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), p);
520
-            }
521
-            else
522
-              return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p);
473
+          const long gcode_N = strtol(npos + 1, nullptr, 10);
523 474
 
524
-            last_N[p] = gcode_N;
475
+          if (gcode_N != serial.last_N + 1 && !M110) {
476
+            // In case of error on a serial port, don't prevent other serial port from making progress
477
+            gcode_line_error(PSTR(STR_ERR_LINE_NO), p);
478
+            break;
525 479
           }
526
-          #if ENABLED(SDSUPPORT)
527
-            // Pronterface "M29" and "M29 " has no line number
528
-            else if (card.flag.saving && !is_M29(command))
529
-              return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p);
530
-          #endif
531 480
 
532
-          //
533
-          // Movement commands give an alert when the machine is stopped
534
-          //
535
-
536
-          if (IsStopped()) {
537
-            char* gpos = strchr(command, 'G');
538
-            if (gpos) {
539
-              switch (strtol(gpos + 1, nullptr, 10)) {
540
-                case 0: case 1:
541
-                #if ENABLED(ARC_SUPPORT)
542
-                  case 2: case 3:
543
-                #endif
544
-                #if ENABLED(BEZIER_CURVE_SUPPORT)
545
-                  case 5:
546
-                #endif
547
-                  PORT_REDIRECT(SERIAL_PORTMASK(p));     // Reply to the serial port that sent the command
548
-                  SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
549
-                  LCD_MESSAGEPGM(MSG_STOPPED);
550
-                  break;
551
-              }
481
+          char *apos = strrchr(command, '*');
482
+          if (apos) {
483
+            uint8_t checksum = 0, count = uint8_t(apos - command);
484
+            while (count) checksum ^= command[--count];
485
+            if (strtol(apos + 1, nullptr, 10) != checksum) {
486
+              // In case of error on a serial port, don't prevent other serial port from making progress
487
+              gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), p);
488
+              break;
552 489
             }
553 490
           }
491
+          else {
492
+            // In case of error on a serial port, don't prevent other serial port from making progress
493
+            gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p);
494
+            break;
495
+          }
496
+
497
+          serial.last_N = gcode_N;
498
+        }
499
+        #if ENABLED(SDSUPPORT)
500
+          // Pronterface "M29" and "M29 " has no line number
501
+          else if (card.flag.saving && !is_M29(command)) {
502
+            gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p);
503
+            break;
504
+          }
505
+        #endif
554 506
 
555
-          #if DISABLED(EMERGENCY_PARSER)
556
-            // Process critical commands early
557
-            if (command[0] == 'M') switch (command[3]) {
558
-              case '8': if (command[2] == '0' && command[1] == '1') { wait_for_heatup = false; TERN_(HAS_LCD_MENU, wait_for_user = false); } break;
559
-              case '2': if (command[2] == '1' && command[1] == '1') kill(M112_KILL_STR, nullptr, true); break;
560
-              case '0': if (command[1] == '4' && command[2] == '1') quickstop_stepper(); break;
507
+        //
508
+        // Movement commands give an alert when the machine is stopped
509
+        //
510
+
511
+        if (IsStopped()) {
512
+          char* gpos = strchr(command, 'G');
513
+          if (gpos) {
514
+            switch (strtol(gpos + 1, nullptr, 10)) {
515
+              case 0: case 1:
516
+              #if ENABLED(ARC_SUPPORT)
517
+                case 2: case 3:
518
+              #endif
519
+              #if ENABLED(BEZIER_CURVE_SUPPORT)
520
+                case 5:
521
+              #endif
522
+                PORT_REDIRECT(SERIAL_PORTMASK(p));     // Reply to the serial port that sent the command
523
+                SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
524
+                LCD_MESSAGEPGM(MSG_STOPPED);
525
+                break;
561 526
             }
562
-          #endif
527
+          }
528
+        }
563 529
 
564
-          #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0
565
-            last_command_time = ms;
566
-          #endif
530
+        #if DISABLED(EMERGENCY_PARSER)
531
+          // Process critical commands early
532
+          if (command[0] == 'M') switch (command[3]) {
533
+            case '8': if (command[2] == '0' && command[1] == '1') { wait_for_heatup = false; TERN_(HAS_LCD_MENU, wait_for_user = false); } break;
534
+            case '2': if (command[2] == '1' && command[1] == '1') kill(M112_KILL_STR, nullptr, true); break;
535
+            case '0': if (command[1] == '4' && command[2] == '1') quickstop_stepper(); break;
536
+          }
537
+        #endif
567 538
 
568
-          // Add the command to the queue
569
-          _enqueue(serial_line_buffer[p], true
570
-            #if HAS_MULTI_SERIAL
571
-              , p
572
-            #endif
573
-          );
574
-        }
575
-        else
576
-          process_stream_char(serial_char, serial_input_state[p], serial_line_buffer[p], serial_count[p]);
539
+        #if NO_TIMEOUTS > 0
540
+          last_command_time = ms;
541
+        #endif
577 542
 
578
-      } // char_count loop
543
+        // Add the command to the queue
544
+        ring_buffer.enqueue(serial.line_buffer, false
545
+          #if HAS_MULTI_SERIAL
546
+            , p
547
+          #endif
548
+        );
549
+      }
550
+      else
551
+        process_stream_char(serial_char, serial.input_state, serial.line_buffer, serial.count);
579 552
 
580 553
     } // NUM_SERIAL loop
581 554
   } // queue has space, serial has data
@@ -595,33 +568,35 @@ void GCodeQueue::get_serial_commands() {
595 568
     if (!IS_SD_PRINTING()) return;
596 569
 
597 570
     int sd_count = 0;
598
-    while (length < BUFSIZE && !card.eof()) {
571
+    while (!ring_buffer.full() && !card.eof()) {
599 572
       const int16_t n = card.get();
600 573
       const bool card_eof = card.eof();
601 574
       if (n < 0 && !card_eof) { SERIAL_ERROR_MSG(STR_SD_ERR_READ); continue; }
602 575
 
576
+      CommandLine &command = ring_buffer.commands[ring_buffer.index_w];
603 577
       const char sd_char = (char)n;
604 578
       const bool is_eol = ISEOL(sd_char);
605 579
       if (is_eol || card_eof) {
606 580
 
581
+
607 582
         // Reset stream state, terminate the buffer, and commit a non-empty command
608 583
         if (!is_eol && sd_count) ++sd_count;          // End of file with no newline
609
-        if (!process_line_done(sd_input_state, command_buffer[index_w], sd_count)) {
584
+        if (!process_line_done(sd_input_state, command.buffer, sd_count)) {
610 585
 
611 586
           // M808 L saves the sdpos of the next line. M808 loops to a new sdpos.
612
-          TERN_(GCODE_REPEAT_MARKERS, repeat.early_parse_M808(command_buffer[index_w]));
587
+          TERN_(GCODE_REPEAT_MARKERS, repeat.early_parse_M808(command.buffer));
613 588
 
614 589
           // Put the new command into the buffer (no "ok" sent)
615
-          _commit_command(false);
590
+          ring_buffer.commit_command(true);
616 591
 
617
-          // Prime Power-Loss Recovery for the NEXT _commit_command
592
+          // Prime Power-Loss Recovery for the NEXT commit_command
618 593
           TERN_(POWER_LOSS_RECOVERY, recovery.cmd_sdpos = card.getIndex());
619 594
         }
620 595
 
621 596
         if (card.eof()) card.fileHasFinished();         // Handle end of file reached
622 597
       }
623 598
       else
624
-        process_stream_char(sd_char, sd_input_state, command_buffer[index_w], sd_count);
599
+        process_stream_char(sd_char, sd_input_state, command.buffer, sd_count);
625 600
     }
626 601
   }
627 602
 
@@ -634,6 +609,7 @@ void GCodeQueue::get_serial_commands() {
634 609
  *  - The SD card file being actively printed
635 610
  */
636 611
 void GCodeQueue::get_available_commands() {
612
+  if (ring_buffer.full()) return;
637 613
 
638 614
   get_serial_commands();
639 615
 
@@ -649,13 +625,13 @@ void GCodeQueue::advance() {
649 625
   if (process_injected_command_P() || process_injected_command()) return;
650 626
 
651 627
   // Return if the G-code buffer is empty
652
-  if (!length) return;
628
+  if (ring_buffer.empty()) return;
653 629
 
654 630
   #if ENABLED(SDSUPPORT)
655 631
 
656 632
     if (card.flag.saving) {
657
-      char* command = command_buffer[index_r];
658
-      if (is_M29(command)) {
633
+      char * const cmd = ring_buffer.peek_next_command_string();
634
+      if (is_M29(cmd)) {
659 635
         // M29 closes the file
660 636
         card.closefile();
661 637
         SERIAL_ECHOLNPGM(STR_FILE_SAVED);
@@ -673,7 +649,7 @@ void GCodeQueue::advance() {
673 649
       }
674 650
       else {
675 651
         // Write the string from the read buffer to SD
676
-        card.write_command(command);
652
+        card.write_command(cmd);
677 653
         if (card.flag.logging)
678 654
           gcode.process_next_command(); // The card is saving because it's logging
679 655
         else
@@ -690,7 +666,5 @@ void GCodeQueue::advance() {
690 666
   #endif // SDSUPPORT
691 667
 
692 668
   // The queue may be reset by a command handler or by code invoked by idle() within a handler
693
-  --length;
694
-  if (++index_r >= BUFSIZE) index_r = 0;
695
-
669
+  ring_buffer.advance_pos(ring_buffer.index_r, -1);
696 670
 }

+ 67
- 32
Marlin/src/gcode/queue.h View File

@@ -31,41 +31,84 @@
31 31
 class GCodeQueue {
32 32
 public:
33 33
   /**
34
-   * GCode line number handling. Hosts may include line numbers when sending
35
-   * commands to Marlin, and lines will be checked for sequentiality.
36
-   * M110 N<int> sets the current line number.
34
+   * The buffers per serial port.
37 35
    */
36
+  struct SerialState {
37
+    /**
38
+     * GCode line number handling. Hosts may include line numbers when sending
39
+     * commands to Marlin, and lines will be checked for sequentiality.
40
+     * M110 N<int> sets the current line number.
41
+     */
42
+    long last_N;
43
+    int count;                        //!< Number of characters read in the current line of serial input
44
+    char line_buffer[MAX_CMD_SIZE];   //!< The current line accumulator
45
+    uint8_t input_state;              //!< The input state
46
+  };
38 47
 
39
-  static long last_N[NUM_SERIAL];
48
+  static SerialState serial_state[NUM_SERIAL]; //!< Serial states for each serial port
40 49
 
41 50
   /**
42 51
    * GCode Command Queue
43
-   * A simple ring buffer of BUFSIZE command strings.
52
+   * A simple (circular) ring buffer of BUFSIZE command strings.
44 53
    *
45 54
    * Commands are copied into this buffer by the command injectors
46 55
    * (immediate, serial, sd card) and they are processed sequentially by
47 56
    * the main loop. The gcode.process_next_command method parses the next
48 57
    * command and hands off execution to individual handler functions.
49 58
    */
50
-  static uint8_t length,  // Count of commands in the queue
51
-                 index_r; // Ring buffer read position
52
-
53
-  static char command_buffer[BUFSIZE][MAX_CMD_SIZE];
59
+  struct CommandLine {
60
+    char buffer[MAX_CMD_SIZE];                    //!< The command buffer
61
+    bool skip_ok;                                 //!< Skip sending ok when command is processed?
62
+    TERN_(HAS_MULTI_SERIAL, serial_index_t port); //!< Serial port the command was received on
63
+  };
54 64
 
55 65
   /**
56
-   * The port that the command was received on
66
+   * A handy ring buffer type
57 67
    */
58
-  #if HAS_MULTI_SERIAL
59
-    static serial_index_t port[BUFSIZE];
60
-  #endif
61
-  static inline serial_index_t command_port() { return TERN0(HAS_MULTI_SERIAL, port[index_r]); }
68
+  struct RingBuffer {
69
+    uint8_t length,                 //!< Number of commands in the queue
70
+            index_r,                //!< Ring buffer's read position
71
+            index_w;                //!< Ring buffer's write position
72
+    CommandLine commands[BUFSIZE];  //!< The ring buffer of commands
73
+
74
+    inline serial_index_t command_port() const { return TERN0(HAS_MULTI_SERIAL, commands[index_r].port); }
75
+
76
+    inline void clear() { length = index_r = index_w = 0; }
77
+
78
+    void advance_pos(uint8_t &p, const int inc) { if (++p >= BUFSIZE) p = 0; length += inc; }
79
+
80
+    void commit_command(bool skip_ok
81
+      #if HAS_MULTI_SERIAL
82
+        , serial_index_t serial_ind=-1
83
+      #endif
84
+    );
85
+
86
+    bool enqueue(const char* cmd, bool skip_ok = true
87
+      #if HAS_MULTI_SERIAL
88
+        , serial_index_t serial_ind=-1
89
+      #endif
90
+    );
91
+
92
+    void ok_to_send();
93
+
94
+    inline bool full(uint8_t cmdCount=1) const { return length > (BUFSIZE - cmdCount); }
95
+
96
+    inline bool empty() const { return length == 0; }
62 97
 
63
-  GCodeQueue();
98
+    inline CommandLine& peek_next_command() { return commands[index_r]; }
99
+
100
+    inline char* peek_next_command_string() { return peek_next_command().buffer; }
101
+  };
102
+
103
+  /**
104
+   * The ring buffer of commands
105
+   */
106
+  static RingBuffer ring_buffer;
64 107
 
65 108
   /**
66 109
    * Clear the Marlin command queue
67 110
    */
68
-  static void clear();
111
+  static void clear() { ring_buffer.clear(); }
69 112
 
70 113
   /**
71 114
    * Next Injected Command (PROGMEM) pointer. (nullptr == empty)
@@ -112,7 +155,7 @@ public:
112 155
   /**
113 156
    * Check whether there are any commands yet to be executed
114 157
    */
115
-  static bool has_commands_queued();
158
+  static bool has_commands_queued() { return ring_buffer.length || injected_commands_P || injected_commands[0]; }
116 159
 
117 160
   /**
118 161
    * Get the next command in the queue, optionally log it to SD, then dispatch it
@@ -136,7 +179,7 @@ public:
136 179
    *   P<int>  Planner space remaining
137 180
    *   B<int>  Block queue space remaining
138 181
    */
139
-  static void ok_to_send();
182
+  static inline void ok_to_send() { ring_buffer.ok_to_send(); }
140 183
 
141 184
   /**
142 185
    * Clear the serial line and request a resend of
@@ -144,9 +187,12 @@ public:
144 187
    */
145 188
   static void flush_and_request_resend();
146 189
 
147
-private:
190
+  /**
191
+   * (Re)Set the current line number for the last received command
192
+   */
193
+  static inline void set_current_line_number(long n) { serial_state[ring_buffer.command_port()].last_N = n; }
148 194
 
149
-  static uint8_t index_w;  // Ring buffer write position
195
+private:
150 196
 
151 197
   static void get_serial_commands();
152 198
 
@@ -154,18 +200,6 @@ private:
154 200
     static void get_sdcard_commands();
155 201
   #endif
156 202
 
157
-  static void _commit_command(bool say_ok
158
-    #if HAS_MULTI_SERIAL
159
-      , serial_index_t serial_ind=-1
160
-    #endif
161
-  );
162
-
163
-  static bool _enqueue(const char* cmd, bool say_ok=false
164
-    #if HAS_MULTI_SERIAL
165
-      , serial_index_t serial_ind=-1
166
-    #endif
167
-  );
168
-
169 203
   // Process the next "immediate" command (PROGMEM)
170 204
   static bool process_injected_command_P();
171 205
 
@@ -180,6 +214,7 @@ private:
180 214
 
181 215
   static void gcode_line_error(PGM_P const err, const serial_index_t serial_ind);
182 216
 
217
+  friend class GcodeSuite;
183 218
 };
184 219
 
185 220
 extern GCodeQueue queue;

+ 1
- 1
Marlin/src/lcd/extui/lib/mks_ui/draw_keyboard.cpp View File

@@ -162,7 +162,7 @@ static void lv_kb_event_cb(lv_obj_t *kb, lv_event_t event) {
162 162
           draw_return_ui();
163 163
           break;
164 164
         case GCodeCommand:
165
-          if (queue.length <= (BUFSIZE - 3)) {
165
+          if (!queue.ring_buffer.full(3)) {
166 166
             // Hook anything that goes to the serial port
167 167
             MYSERIAL0.setHook(lv_serial_capt_hook, lv_eom_hook, 0);
168 168
             queue.enqueue_one_now(ret_ta_txt);

+ 1
- 1
Marlin/src/lcd/extui/lib/mks_ui/draw_manuaLevel.cpp View File

@@ -46,7 +46,7 @@ static void event_handler(lv_obj_t *obj, lv_event_t event) {
46 46
 
47 47
   switch (obj->mks_obj_id) {
48 48
     case ID_M_POINT1 ... ID_M_POINT5:
49
-      if (queue.length == 0) {
49
+      if (queue.ring_buffer.empty()) {
50 50
         if (uiCfg.leveling_first_time) {
51 51
           uiCfg.leveling_first_time = false;
52 52
           queue.inject_P(G28_STR);

+ 1
- 1
Marlin/src/lcd/extui/lib/mks_ui/draw_move_motor.cpp View File

@@ -54,7 +54,7 @@ enum {
54 54
 static void event_handler(lv_obj_t *obj, lv_event_t event) {
55 55
   char str_1[16];
56 56
   if (event != LV_EVENT_RELEASED) return;
57
-  if (queue.length <= (BUFSIZE - 3)) {
57
+  if (!queue.ring_buffer.full(3)) {
58 58
     bool do_inject = true;
59 59
     float dist = uiCfg.move_dist;
60 60
     switch (obj->mks_obj_id) {

+ 2
- 2
Marlin/src/lcd/extui/lib/mks_ui/wifi_module.cpp View File

@@ -1613,7 +1613,7 @@ void wifi_rcv_handle() {
1613 1613
       if (wifiTransError.flag != 0x1) WIFI_IO1_RESET();
1614 1614
       getDataF = 1;
1615 1615
     }
1616
-    if (need_ok_later &&  (queue.length < BUFSIZE)) {
1616
+    if (need_ok_later && !queue.ring_buffer.full()) {
1617 1617
       need_ok_later = false;
1618 1618
       send_to_wifi((uint8_t *)"ok\r\n", strlen("ok\r\n"));
1619 1619
     }
@@ -1772,7 +1772,7 @@ void get_wifi_commands() {
1772 1772
   static int wifi_read_count = 0;
1773 1773
 
1774 1774
   if (espGcodeFifo.wait_tick > 5) {
1775
-    while ((queue.length < BUFSIZE) && (espGcodeFifo.r != espGcodeFifo.w)) {
1775
+    while (!queue.ring_buffer.full() && (espGcodeFifo.r != espGcodeFifo.w)) {
1776 1776
 
1777 1777
       espGcodeFifo.wait_tick = 0;
1778 1778
 

+ 59
- 0
docs/Queue.md View File

@@ -0,0 +1,59 @@
1
+# Marlin's command queue concept
2
+
3
+Marlin Firmware processes G-code commands as they arrive from multiple sources, including the SD card and one or more serial ports such as USB-connected hosts, WiFi, Bluetooth, and so on.
4
+
5
+Marlin is also continuously processing the commands at the front of the queue, converting them into signals for many physical actuators such as motors, heaters, lasers, and RGB LEDs.
6
+
7
+The firmware needs to maintain continuity and timing so the command senders remain unblocked, while still performing physical movements and other actions in real-time, respecting the physical limits of stepper motors and other peripherals.
8
+
9
+To keep things flowing Marlin feeds a single queue of G-code commands from all inputs, inserting them in the order received. Movement commands immediately go into the Planner Buffer, if there is room. The buffering of a move is considered the completion of the command, so if a non-movement command has to occur after a move is done, and not just after a move is buffered, then there has to be an `M400` to wait for the Planner Buffer to finish.
10
+
11
+Whenever the command queue gets full the sender needs to wait for space to open up, and the host may need to re-send the last command again. Marlin does some handshaking to keep the host informed during a print job, described below.
12
+
13
+An opposite problem called "planner starvation" occurs when Marlin receives many short and fast moves in a row so the Planner Buffer gets completed very quickly. In this case the host can't send commands fast enough to prevent the Planner Buffer from emptying out. Planner starvation causes obvious stuttering and is commonly seen on overloaded deltabots during small curves. Marlin has strategies to mitigate this issue, but sometimes a model has to be re-sliced (or the G-code has to be post-processed with Arc Welder) just to stay within the machine's inherent limits.
14
+
15
+Here's a basic flowchart of Marlin command processing:
16
+```
17
++------+                                Marlin's GCodeQueue
18
+|      |                             +--------------------------------------+     +-----------+
19
+| Host |                             |  SerialState         RingBuffer      |     |           |
20
+|      |             Marlin          |  NUM_SERIAL           BUF_SIZE       |     | Marlin    |
21
++--+---+        R/TX_BUFFER_SIZE     |    +---+        +------------------+ |     |           |
22
+   |             +------------+      |    |   |        |                  | |     | GCode     |
23
+   |             |            |      |    |   |        |   MAX_CMD_SIZE   +-+-----> processor |
24
+   |             | Platform   |      |    |   | On EOL | +--------------+ | r_pos |           |
25
+   +-------------> serial's   +----------->   +--------> |   G-code     | | |     +-----------+
26
+                 | buffer     |      |    |   |  w_pos | |   command    | | |
27
+                 |            |      |    |   |        | |    line      | | |
28
+                 +------------+      |    +---+        | +--------------+ | |
29
+                                     |  Line buffer    |    x BUF_SIZE    | |
30
+                                     |                 |                  | |
31
+                                     |                 |                  | |
32
+                                     |                 |                  | |
33
+                                     |                 |                  | |
34
+                                     |                 +------------------+ |
35
+                                     |                                      |
36
+                                     |                                      |
37
+                                     |                                      |
38
+                                     +--------------------------------------+
39
+```
40
+
41
+Marlin is a single-threaded application with a main `loop()` that manages the command queue and an `idle()` routine that manages the hardware. The command queue is handled in two stages:
42
+1. The `idle()` routine reads all inputs and attempts to enqueue any completed command lines.
43
+2. The main `loop()` gets the command at the front the G-code queue (if any) and runs it. Each G-code command blocks the main loop, preventing the queue from advancing until it returns. To keep essential tasks and the UI running, any commands that run a long process need to call `idle()` frequently.
44
+
45
+## Synchronization
46
+
47
+To maintain synchronization Marlin replies "`ok`" to the host as soon as the command has been enqueued. This lets the host know that it can send another command, and well-behaved hosts will wait for this message. With `ADVANCED_OK` enabled the `ok` message includes extra information (such as the number of slots left in the queue).
48
+
49
+If no data is available on the serial buffer, Marlin can be configured to periodically send a "`wait`" message to the host. This was the only method of "host keepalive" provided in Marlin 1.0, but today the better options are `HOST_KEEPALIVE` and `ADVANCED_OK`.
50
+
51
+## Limitation of the design
52
+
53
+Some limitations to the design are evident:
54
+1. Whenever the G-code processor is busy processing a command, the G-code queue cannot advance.
55
+2. A long command like `G29` causes commands to pile up and to fill the queue, making the host wait.
56
+3. Each serial input requires a buffer large enough for a complete G-code line. This is set by `MAX_CMD_SIZE` with a default value of 96.
57
+4. Since serial buffer sizes are likely used as ring buffers themselves, as an optimization their sizes must be a power of 2 (64 or 128 bytes recommended).
58
+5. If a host sends too much G-code at once it can saturate the `GCodeQueue`. This doesn't do anything to improve the processing rate of Marlin since only one command can be dispatched per loop iteration.
59
+6. With the previous point in mind, it's clear that the longstanding wisdom that you don't need a large `BUF_SIZE` is not just apocryphal. The default value of 4 is typically just fine for a single serial port. (And, if you decide to send a `G25` to pause the machine, the wait will be much shorter!)

Loading…
Cancel
Save