diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 987ac293b2d2e..1bc1fd4e55cc6 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2012,6 +2012,13 @@
//#define SERIAL_STATS_DROPPED_RX
#endif
+// Monitor RX buffer usage
+// This will dump an error to the serial port in case of overflow of the serial receive buffer
+// If it happens, increase the RX_BUFFER_SIZE value
+// Not supported on all platform.
+#define RX_BUFFER_MONITOR
+
+
/**
* Emergency Command Parser
*
diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp
index a04993ca35edf..fca677c7981e0 100644
--- a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp
+++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp
@@ -92,12 +92,11 @@ int MarlinSerialUSB::read() {
return c;
}
-bool MarlinSerialUSB::available() {
- /* If Pending chars */
- return pending_char >= 0 ||
- /* or USB CDC enumerated and configured on the PC side and some
- bytes where sent to us */
- (usb_task_cdc_isenabled() && udi_cdc_is_rx_ready());
+int MarlinSerialUSB::available() {
+ if (pending_char > 0) return pending_char;
+ return pending_char == 0 ||
+ // or USB CDC enumerated and configured on the PC side and some bytes where sent to us */
+ (usb_task_cdc_isenabled() && udi_cdc_is_rx_ready());
}
void MarlinSerialUSB::flush() { }
diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.h b/Marlin/src/HAL/DUE/MarlinSerialUSB.h
index 5281a006b1559..f9cea29869604 100644
--- a/Marlin/src/HAL/DUE/MarlinSerialUSB.h
+++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.h
@@ -39,7 +39,7 @@ struct MarlinSerialUSB {
int peek();
int read();
void flush();
- bool available();
+ int available();
size_t write(const uint8_t c);
#if ENABLED(SERIAL_STATS_DROPPED_RX)
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 8aa6cdd6c8921..718d075e9c264 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -407,7 +407,7 @@ void startOrResumeJob() {
*/
inline void manage_inactivity(const bool ignore_stepper_queue=false) {
- if (queue.length < BUFSIZE) queue.get_available_commands();
+ queue.get_available_commands();
const millis_t ms = millis();
diff --git a/Marlin/src/core/bug_on.h b/Marlin/src/core/bug_on.h
new file mode 100644
index 0000000000000..8869be8d284fb
--- /dev/null
+++ b/Marlin/src/core/bug_on.h
@@ -0,0 +1,37 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Copyright (c) 2021 X-Ryl669 [https://blog.cyril.by]
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+// We need SERIAL_ECHOPAIR and macros.h
+#include "serial.h"
+
+#if ENABLED(POSTMORTEM_DEBUGGING)
+ // Useful macro for stopping the CPU on an unexpected condition
+ // This is used like SERIAL_ECHOPAIR, that is: a key-value call of the local variables you want
+ // to dump to the serial port before stopping the CPU.
+ #define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": "); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0)
+#elif ENABLED(MARLIN_DEV_MODE)
+ // Don't stop the CPU here, but at least dump the bug on the serial port
+ #define BUG_ON(V...) do { SERIAL_ECHOPAIR(ONLY_FILENAME, __LINE__, ": BUG!\n"); SERIAL_ECHOLNPAIR(V); SERIAL_FLUSHTX(); } while(0)
+#else
+ // Release mode, let's ignore the bug
+ #define BUG_ON(V...) NOOP
+#endif
diff --git a/Marlin/src/core/macros.h b/Marlin/src/core/macros.h
index 16e18ac9023de..c65aebdcdf85d 100644
--- a/Marlin/src/core/macros.h
+++ b/Marlin/src/core/macros.h
@@ -349,6 +349,32 @@
#define CALL_IF_EXISTS(Return, That, Method, ...) \
static_cast(Private::Call_ ## Method(That, ##__VA_ARGS__))
+
+ // Compile time string manipulation
+ namespace CompileTimeString {
+ // Simple compile-time parser to find the position of the end of a string
+ constexpr const char* findStringEnd(const char *str) {
+ return *str ? findStringEnd(str + 1) : str;
+ }
+
+ // Check whether a string contains a slash
+ constexpr bool containsSlash(const char *str) {
+ return *str == '/' ? true : (*str ? containsSlash(str + 1) : false);
+ }
+ // Find the last position of the slash
+ constexpr const char* findLastSlashPos(const char* str) {
+ return *str == '/' ? (str + 1) : findLastSlashPos(str - 1);
+ }
+ // Compile time evaluation of the last part of a file path
+ // Typically used to shorten the path to file in compiled strings
+ // CompileTimeString::fileName(__FILE__) returns "macros.h" and not /path/to/Marlin/src/core/macros.h
+ constexpr const char* fileName(const char* str) {
+ return containsSlash(str) ? findLastSlashPos(findStringEnd(str)) : str;
+ }
+ }
+
+ #define ONLY_FILENAME CompileTimeString::fileName(__FILE__)
+
#else
#define MIN_2(a,b) ((a)<(b)?(a):(b))
diff --git a/Marlin/src/core/serial_base.h b/Marlin/src/core/serial_base.h
index 83b03cc7b608f..418bb557e7cbc 100644
--- a/Marlin/src/core/serial_base.h
+++ b/Marlin/src/core/serial_base.h
@@ -79,7 +79,7 @@ struct SerialBase {
void end() { static_cast(this)->end(); }
/** Check for available data from the port
@param index The port index, usually 0 */
- bool available(uint8_t index = 0) { return static_cast(this)->available(index); }
+ int available(uint8_t index = 0) { return static_cast(this)->available(index); }
/** Read a value from the port
@param index The port index, usually 0 */
int read(uint8_t index = 0) { return static_cast(this)->read(index); }
diff --git a/Marlin/src/core/serial_hook.h b/Marlin/src/core/serial_hook.h
index 5226a51f167a2..2fa58b27863ce 100644
--- a/Marlin/src/core/serial_hook.h
+++ b/Marlin/src/core/serial_hook.h
@@ -39,7 +39,7 @@ struct BaseSerial : public SerialBase< BaseSerial >, public SerialT {
void msgDone() {}
// We don't care about indices here, since if one can call us, it's the right index anyway
- bool available(uint8_t) { return (bool)SerialT::available(); }
+ int available(uint8_t) { return (int)SerialT::available(); }
int read(uint8_t) { return (int)SerialT::read(); }
bool connected() { return CALL_IF_EXISTS(bool, static_cast(this), connected);; }
void flushTX() { CALL_IF_EXISTS(void, static_cast(this), flushTX); }
@@ -74,13 +74,13 @@ struct ConditionalSerial : public SerialBase< ConditionalSerial > {
void end() { out.end(); }
void msgDone() {}
- bool connected() { return CALL_IF_EXISTS(bool, &out, connected); }
- void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
+ bool connected() { return CALL_IF_EXISTS(bool, &out, connected); }
+ void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
- bool available(uint8_t ) { return (bool)out.available(); }
- int read(uint8_t ) { return (int)out.read(); }
- bool available() { return (bool)out.available(); }
- int read() { return (int)out.read(); }
+ int available(uint8_t ) { return (int)out.available(); }
+ int read(uint8_t ) { return (int)out.read(); }
+ int available() { return (int)out.available(); }
+ int read() { return (int)out.read(); }
ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {}
@@ -102,9 +102,9 @@ struct ForwardSerial : public SerialBase< ForwardSerial > {
bool connected() { return Private::HasMember_connected::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
- bool available(uint8_t) { return (bool)out.available(); }
+ int available(uint8_t) { return (int)out.available(); }
int read(uint8_t) { return (int)out.read(); }
- bool available() { return (bool)out.available(); }
+ int available() { return (int)out.available(); }
int read() { return (int)out.read(); }
ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
@@ -130,7 +130,7 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial >, public Seria
if (eofHook) eofHook(userPointer);
}
- bool available(uint8_t) { return (bool)SerialT::available(); }
+ int available(uint8_t) { return (int)SerialT::available(); }
int read(uint8_t) { return (int)SerialT::read(); }
using SerialT::available;
using SerialT::read;
@@ -191,14 +191,14 @@ struct MultiSerial : public SerialBase< MultiSerial= 0 + offset && index < step + offset)
return serial0.available(index);
else if (index >= step + offset && index < 2 * step + offset)
return serial1.available(index);
return false;
}
- NO_INLINE int read(uint8_t index) {
+ int read(uint8_t index) {
if (index >= 0 + offset && index < step + offset)
return serial0.read(index);
else if (index >= step + offset && index < 2 * step + offset)
diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp
index 9ac2380e79640..cea7b526ce4f8 100644
--- a/Marlin/src/gcode/calibrate/M100.cpp
+++ b/Marlin/src/gcode/calibrate/M100.cpp
@@ -182,16 +182,14 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
}
}
- void M100_dump_routine(PGM_P const title, const char * const start, const char * const end) {
+ void M100_dump_routine(PGM_P const title, const GCodeQueue::CommandQueue * start, const GCodeQueue::CommandQueue * end) {
serialprintPGM(title);
SERIAL_EOL();
- //
- // Round the start and end locations to produce full lines of output
- //
- dump_free_memory(
- (char*)(uintptr_t(uint32_t(start) & ~0xFUL)), // Align to 16-byte boundary
- (char*)(uintptr_t(uint32_t(end) | 0xFUL)) // Align end_free_memory to the 15th byte (at or above end_free_memory)
- );
+ while (start < end)
+ {
+ SERIAL_ECHO(start->buffer);
+ ++start;
+ }
}
#endif // M100_FREE_MEMORY_DUMPER
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 77ac1fbff86b1..1babdd0a01c25 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -264,7 +264,7 @@ void GcodeSuite::dwell(millis_t time) {
// Placeholders for non-migrated codes
//
#if ENABLED(M100_FREE_MEMORY_WATCHER)
- extern void M100_dump_routine(PGM_P const title, const char * const start, const char * const end);
+ extern void M100_dump_routine(PGM_P const title, const GCodeQueue::CommandQueue * start, const GCodeQueue::CommandQueue * end);
#endif
/**
@@ -995,25 +995,27 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
* This is called from the main loop()
*/
void GcodeSuite::process_next_command() {
- char * const current_command = queue.command_buffer[queue.index_r];
+ GCodeQueue::CommandQueue & command = queue.peek_next_command();
- PORT_REDIRECT(SERIAL_PORTMASK(queue.port[queue.index_r]));
+ #if HAS_MULTI_SERIAL
+ PORT_REDIRECT(SERIAL_PORTMASK(command.port));
+ #endif
#if ENABLED(POWER_LOSS_RECOVERY)
- recovery.queue_index_r = queue.index_r;
+ recovery.queue_index_r = queue.ring_buffer.index_r;
#endif
if (DEBUGGING(ECHO)) {
SERIAL_ECHO_START();
- SERIAL_ECHOLN(current_command);
+ SERIAL_ECHOLN(command.buffer);
#if ENABLED(M100_FREE_MEMORY_DUMPER)
- SERIAL_ECHOPAIR("slot:", queue.index_r);
- M100_dump_routine(PSTR(" Command Queue:"), &queue.command_buffer[0][0], &queue.command_buffer[BUFSIZE - 1][MAX_CMD_SIZE - 1]);
+ SERIAL_ECHOPAIR("slot:", queue.ring_buffer.index_r);
+ M100_dump_routine(PSTR(" Command Queue:"), &queue.ring_buffer.commands[0], &queue.ring_buffer.commands[BUFSIZE]);
#endif
}
// Parse the next command in the queue
- parser.parse(current_command);
+ parser.parse(command.buffer);
process_parsed_command();
}
diff --git a/Marlin/src/gcode/host/M110.cpp b/Marlin/src/gcode/host/M110.cpp
index b12b38ea0f124..2634b198978d9 100644
--- a/Marlin/src/gcode/host/M110.cpp
+++ b/Marlin/src/gcode/host/M110.cpp
@@ -29,6 +29,6 @@
void GcodeSuite::M110() {
if (parser.seenval('N'))
- queue.last_N[queue.command_port()] = parser.value_long();
+ queue.set_current_line_number(parser.value_long());
}
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index 6cb21e6c61deb..b7aa9189fe28d 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -35,6 +35,7 @@ GCodeQueue queue;
#include "../module/planner.h"
#include "../module/temperature.h"
#include "../MarlinCore.h"
+#include "../core/bug_on.h"
#if ENABLED(PRINTER_EVENT_LEDS)
#include "../feature/leds/printer_event_leds.h"
@@ -59,44 +60,15 @@ GCodeQueue queue;
// Frequently used G-code strings
PGMSTR(G28_STR, "G28");
-/**
- * GCode line number handling. Hosts may opt to include line numbers when
- * sending commands to Marlin, and lines will be checked for sequentiality.
- * M110 N sets the current line number.
- */
-long GCodeQueue::last_N[NUM_SERIAL];
-/**
- * GCode Command Queue
- * A simple ring buffer of BUFSIZE command strings.
- *
- * Commands are copied into this buffer by the command injectors
- * (immediate, serial, sd card) and they are processed sequentially by
- * the main loop. The gcode.process_next_command method parses the next
- * command and hands off execution to individual handler functions.
- */
-uint8_t GCodeQueue::length = 0, // Count of commands in the queue
- GCodeQueue::index_r = 0, // Ring buffer read position
- GCodeQueue::index_w = 0; // Ring buffer write position
+GCodeQueue::PerSerial GCodeQueue::per_serial[NUM_SERIAL] = { 0 };
+GCodeQueue::RingBuffer GCodeQueue::ring_buffer = { 0 };
-char GCodeQueue::command_buffer[BUFSIZE][MAX_CMD_SIZE];
-
-/*
- * The port that the command was received on
- */
-#if HAS_MULTI_SERIAL
- serial_index_t GCodeQueue::port[BUFSIZE];
-#endif
/**
* Serial command injection
*/
-// Number of characters read in the current line of serial input
-static int serial_count[NUM_SERIAL] = { 0 };
-
-bool send_ok[BUFSIZE];
-
/**
* Next Injected PROGMEM Command pointer. (nullptr == empty)
* Internal commands are enqueued ahead of serial / SD commands.
@@ -108,38 +80,16 @@ PGM_P GCodeQueue::injected_commands_P; // = nullptr
*/
char GCodeQueue::injected_commands[64]; // = { 0 }
-GCodeQueue::GCodeQueue() {
- // Send "ok" after commands by default
- LOOP_L_N(i, COUNT(send_ok)) send_ok[i] = true;
-}
-
-/**
- * Check whether there are any commands yet to be executed
- */
-bool GCodeQueue::has_commands_queued() {
- return queue.length || injected_commands_P || injected_commands[0];
-}
-/**
- * Clear the Marlin command queue
- */
-void GCodeQueue::clear() {
- index_r = index_w = length = 0;
-}
-
-/**
- * Once a new command is in the ring buffer, call this to commit it
- */
-void GCodeQueue::_commit_command(bool say_ok
+void GCodeQueue::RingBuffer::commit_command(bool skip_ok
#if HAS_MULTI_SERIAL
, serial_index_t serial_ind/*=-1*/
#endif
-) {
- send_ok[index_w] = say_ok;
- TERN_(HAS_MULTI_SERIAL, port[index_w] = serial_ind);
+ ) {
+ commands[index_w].skip_ok = skip_ok;
+ TERN_(HAS_MULTI_SERIAL, commands[index_w].port = serial_ind);
TERN_(POWER_LOSS_RECOVERY, recovery.commit_sdpos(index_w));
- if (++index_w >= BUFSIZE) index_w = 0;
- length++;
+ advance_pos(index_w, 1);
}
/**
@@ -147,14 +97,14 @@ void GCodeQueue::_commit_command(bool say_ok
* Return true if the command was successfully added.
* Return false for a full buffer, or if the 'command' is a comment.
*/
-bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
+bool GCodeQueue::RingBuffer::enqueue(const char* cmd, bool skip_ok/*=true*/
#if HAS_MULTI_SERIAL
, serial_index_t serial_ind/*=-1*/
#endif
) {
if (*cmd == ';' || length >= BUFSIZE) return false;
- strcpy(command_buffer[index_w], cmd);
- _commit_command(say_ok
+ strcpy(commands[index_w].buffer, cmd);
+ commit_command(skip_ok
#if HAS_MULTI_SERIAL
, serial_ind
#endif
@@ -174,7 +124,7 @@ bool GCodeQueue::enqueue_one(const char* cmd) {
if (*cmd == 0 || ISEOL(*cmd)) return true;
- if (_enqueue(cmd)) {
+ if (ring_buffer.enqueue(cmd)) {
SERIAL_ECHO_MSG(STR_ENQUEUEING, cmd, "\"");
return true;
}
@@ -252,7 +202,7 @@ bool GCodeQueue::enqueue_one_P(PGM_P const pgcode) {
char cmd[i + 1];
memcpy_P(cmd, p, i);
cmd[i] = '\0';
- return _enqueue(cmd);
+ return ring_buffer.enqueue(cmd);
}
/**
@@ -283,16 +233,17 @@ void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
* P Planner space remaining
* B Block queue space remaining
*/
-void GCodeQueue::ok_to_send() {
+void GCodeQueue::RingBuffer::ok_to_send() {
+ CommandQueue & command = commands[index_r];
#if HAS_MULTI_SERIAL
- const serial_index_t serial_ind = command_port();
+ const serial_index_t serial_ind = command.port;
if (serial_ind < 0) return;
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
#endif
- if (!send_ok[index_r]) return;
+ if (command.skip_ok) return;
SERIAL_ECHOPGM(STR_OK);
#if ENABLED(ADVANCED_OK)
- char* p = command_buffer[index_r];
+ char* p = command.buffer;
if (*p == 'N') {
SERIAL_CHAR(' ', *p++);
while (NUMERIC_SIGNED(*p))
@@ -309,25 +260,42 @@ void GCodeQueue::ok_to_send() {
* indicate that a command needs to be re-sent.
*/
void GCodeQueue::flush_and_request_resend() {
- const serial_index_t serial_ind = command_port();
+ const serial_index_t serial_ind = ring_buffer.command_port();
#if HAS_MULTI_SERIAL
if (serial_ind < 0) return; // Never mind. Command came from SD or Flash Drive
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
#endif
SERIAL_FLUSH();
SERIAL_ECHOPGM(STR_RESEND);
- SERIAL_ECHOLN(last_N[serial_ind] + 1);
+ SERIAL_ECHOLN(per_serial[serial_ind].last_N + 1);
}
// Multiserial already handle the dispatch to/from multiple port by itself
inline bool serial_data_available(uint8_t index = SERIAL_ALL) {
if (index == SERIAL_ALL) {
for (index = 0; index < NUM_SERIAL; index++) {
- if (SERIAL_IMPL.available(index)) return true;
+ int a = SERIAL_IMPL.available(index);
+ #if BOTH(RX_BUFFER_MONITOR, RX_BUFFER_SIZE)
+ if (a > RX_BUFFER_SIZE - 2) {
+ PORT_REDIRECT(SERIAL_PORTMASK(index));
+ SERIAL_ERROR_START();
+ SERIAL_ECHOLNPAIR("RX BUF overflow, increase RX_BUFFER_SIZE: ", a);
+ }
+ #endif
+ if (a > 0) return true;
}
return false;
}
- return SERIAL_IMPL.available(index);
+ int a = SERIAL_IMPL.available(index);
+ #if BOTH(RX_BUFFER_MONITOR, RX_BUFFER_SIZE)
+ if (a > RX_BUFFER_SIZE - 2) {
+ PORT_REDIRECT(SERIAL_PORTMASK(index));
+ SERIAL_ERROR_START();
+ SERIAL_ECHOLNPAIR("RX BUF overflow, increase RX_BUFFER_SIZE: ", a);
+ }
+ #endif
+
+ return a > 0;
}
inline int read_serial(const uint8_t index) { return SERIAL_IMPL.read(index); }
@@ -336,10 +304,10 @@ void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_i
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
SERIAL_ERROR_START();
serialprintPGM(err);
- SERIAL_ECHOLN(last_N[serial_ind]);
- while (read_serial(serial_ind) != -1); // Clear out the RX buffer
+ SERIAL_ECHOLN(per_serial[serial_ind].last_N);
+ while (read_serial(serial_ind) != -1); // Clear out the RX buffer. Why don't use flush here ?
flush_and_request_resend();
- serial_count[serial_ind] = 0;
+ per_serial[serial_ind].count = 0;
}
FORCE_INLINE bool is_M29(const char * const cmd) { // matches "M29" & "M29 ", but not "M290", etc
@@ -426,10 +394,6 @@ inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind
* left on the serial port.
*/
void GCodeQueue::get_serial_commands() {
- static char serial_line_buffer[NUM_SERIAL][MAX_CMD_SIZE];
-
- static uint8_t serial_input_state[NUM_SERIAL] = { PS_NORMAL };
-
#if ENABLED(BINARY_FILE_TRANSFER)
if (card.flag.binary_mode) {
/**
@@ -437,7 +401,7 @@ void GCodeQueue::get_serial_commands() {
* receive buffer (which limits the packet size to MAX_CMD_SIZE).
* The receive buffer also limits the packet size for reliable transmission.
*/
- binaryStream[card.transfer_port_index].receive(serial_line_buffer[card.transfer_port_index]);
+ binaryStream[card.transfer_port_index].receive(per_serial[card.transfer_port_index].line_buffer);
return;
}
#endif
@@ -460,7 +424,7 @@ void GCodeQueue::get_serial_commands() {
LOOP_L_N(p, NUM_SERIAL) {
// Check if the queue is full, and exits if it is.
- if (length >= BUFSIZE) return;
+ if (ring_buffer.full()) return;
// No data for this port ? Skip it
if (!serial_data_available(p)) continue;
@@ -472,26 +436,24 @@ void GCodeQueue::get_serial_commands() {
if (c < 0) {
// This should never happen, let's log it
PORT_REDIRECT(SERIAL_PORTMASK(p)); // Reply to the serial port that sent the command
- #if BOTH(MARLIN_DEV_MODE, POSTMORTEM_DEBUGGING)
- // Crash here to get more information why it failed
- BUG_ON("SP available but read -1");
- #else
- SERIAL_ERROR_START();
- SERIAL_ECHOLNPGM(STR_ERR_SERIAL_MISMATCH);
- SERIAL_FLUSH();
- continue;
- #endif
+ // Crash here to get more information why it failed
+ BUG_ON("SP available but read -1");
+ SERIAL_ERROR_START();
+ SERIAL_ECHOLNPGM(STR_ERR_SERIAL_MISMATCH);
+ SERIAL_FLUSH();
+ continue;
}
const char serial_char = (char)c;
+ PerSerial & serial = per_serial[p];
if (ISEOL(serial_char)) {
// Reset our state, continue if the line was empty
- if (process_line_done(serial_input_state[p], serial_line_buffer[p], serial_count[p]))
+ if (process_line_done(serial.input_state, serial.line_buffer, serial.count))
continue;
- char* command = serial_line_buffer[p];
+ char* command = serial.line_buffer;
while (*command == ' ') command++; // Skip leading spaces
char *npos = (*command == 'N') ? command : nullptr; // Require the N parameter to start the line
@@ -507,7 +469,7 @@ void GCodeQueue::get_serial_commands() {
const long gcode_N = strtol(npos + 1, nullptr, 10);
- if (gcode_N != last_N[p] + 1 && !M110) {
+ if (gcode_N != serial.last_N + 1 && !M110) {
// In case of error on a serial port, don't prevent other serial port from making progress
gcode_line_error(PSTR(STR_ERR_LINE_NO), p);
break;
@@ -530,7 +492,7 @@ void GCodeQueue::get_serial_commands() {
break;
}
- last_N[p] = gcode_N;
+ serial.last_N = gcode_N;
}
#if ENABLED(SDSUPPORT)
// Pronterface "M29" and "M29 " has no line number
@@ -577,14 +539,14 @@ void GCodeQueue::get_serial_commands() {
#endif
// Add the command to the queue
- _enqueue(serial_line_buffer[p], true
+ ring_buffer.enqueue(serial.line_buffer, false
#if HAS_MULTI_SERIAL
, p
#endif
);
}
else
- process_stream_char(serial_char, serial_input_state[p], serial_line_buffer[p], serial_count[p]);
+ process_stream_char(serial_char, serial.input_state, serial.line_buffer, serial.count);
} // NUM_SERIAL loop
} // queue has space, serial has data
@@ -604,33 +566,35 @@ void GCodeQueue::get_serial_commands() {
if (!IS_SD_PRINTING()) return;
int sd_count = 0;
- while (length < BUFSIZE && !card.eof()) {
+ while (!ring_buffer.full() && !card.eof()) {
const int16_t n = card.get();
const bool card_eof = card.eof();
if (n < 0 && !card_eof) { SERIAL_ERROR_MSG(STR_SD_ERR_READ); continue; }
+ CommandQueue & command = ring_buffer.commands[ring_buffer.index_w];
const char sd_char = (char)n;
const bool is_eol = ISEOL(sd_char);
if (is_eol || card_eof) {
+
// Reset stream state, terminate the buffer, and commit a non-empty command
if (!is_eol && sd_count) ++sd_count; // End of file with no newline
- if (!process_line_done(sd_input_state, command_buffer[index_w], sd_count)) {
+ if (!process_line_done(sd_input_state, command.buffer, sd_count)) {
// M808 L saves the sdpos of the next line. M808 loops to a new sdpos.
- TERN_(GCODE_REPEAT_MARKERS, repeat.early_parse_M808(command_buffer[index_w]));
+ TERN_(GCODE_REPEAT_MARKERS, repeat.early_parse_M808(command.buffer));
// Put the new command into the buffer (no "ok" sent)
- _commit_command(false);
+ ring_buffer.commit_command(true);
- // Prime Power-Loss Recovery for the NEXT _commit_command
+ // Prime Power-Loss Recovery for the NEXT commit_command
TERN_(POWER_LOSS_RECOVERY, recovery.cmd_sdpos = card.getIndex());
}
if (card.eof()) card.fileHasFinished(); // Handle end of file reached
}
else
- process_stream_char(sd_char, sd_input_state, command_buffer[index_w], sd_count);
+ process_stream_char(sd_char, sd_input_state, command.buffer, sd_count);
}
}
@@ -643,6 +607,7 @@ void GCodeQueue::get_serial_commands() {
* - The SD card file being actively printed
*/
void GCodeQueue::get_available_commands() {
+ if (ring_buffer.full()) return;
get_serial_commands();
@@ -658,12 +623,12 @@ void GCodeQueue::advance() {
if (process_injected_command_P() || process_injected_command()) return;
// Return if the G-code buffer is empty
- if (!length) return;
+ if (ring_buffer.empty()) return;
#if ENABLED(SDSUPPORT)
if (card.flag.saving) {
- char* command = command_buffer[index_r];
+ char* command = ring_buffer.commands[ring_buffer.index_r].buffer;
if (is_M29(command)) {
// M29 closes the file
card.closefile();
@@ -699,6 +664,5 @@ void GCodeQueue::advance() {
#endif // SDSUPPORT
// The queue may be reset by a command handler or by code invoked by idle() within a handler
- --length;
- if (++index_r >= BUFSIZE) index_r = 0;
+ ring_buffer.advance_pos(ring_buffer.index_r, -1);
}
diff --git a/Marlin/src/gcode/queue.h b/Marlin/src/gcode/queue.h
index d677146a7dbeb..5e149f209a184 100644
--- a/Marlin/src/gcode/queue.h
+++ b/Marlin/src/gcode/queue.h
@@ -31,41 +31,124 @@
class GCodeQueue {
public:
/**
- * GCode line number handling. Hosts may include line numbers when sending
- * commands to Marlin, and lines will be checked for sequentiality.
- * M110 N sets the current line number.
+ * The buffers per serial port.
*/
+ struct PerSerial
+ {
+ /**
+ * GCode line number handling. Hosts may include line numbers when sending
+ * commands to Marlin, and lines will be checked for sequentiality.
+ * M110 N sets the current line number.
+ */
+ long last_N;
+ /**
+ * Number of characters read in the current line of serial input
+ */
+ int count;
+
+ /**
+ * The current line accumulator
+ */
+ char line_buffer[MAX_CMD_SIZE];
+
+ /**
+ * The input state
+ */
+ uint8_t input_state;
+ };
- static long last_N[NUM_SERIAL];
+ /**
+ * The array of per serial state variable
+ */
+ static PerSerial per_serial[NUM_SERIAL];
/**
* GCode Command Queue
- * A simple ring buffer of BUFSIZE command strings.
+ * A simple (circular) ring buffer of BUFSIZE command strings.
*
* Commands are copied into this buffer by the command injectors
* (immediate, serial, sd card) and they are processed sequentially by
* the main loop. The gcode.process_next_command method parses the next
* command and hands off execution to individual handler functions.
*/
- static uint8_t length, // Count of commands in the queue
- index_r; // Ring buffer read position
+ struct CommandQueue
+ {
+ /**
+ * The command buffer
+ */
+ char buffer[MAX_CMD_SIZE];
+
+ /**
+ * Skip sending ok when command is processed ?
+ * The logic is inverted here to skip setting all the variables to true upon construction
+ */
+ bool skip_ok;
+
+ #if HAS_MULTI_SERIAL
+ /**
+ * The port that the command was received on
+ */
+ serial_index_t port;
+ #endif
+ };
- static char command_buffer[BUFSIZE][MAX_CMD_SIZE];
/**
- * The port that the command was received on
+ * The ring buffer head
*/
- #if HAS_MULTI_SERIAL
- static serial_index_t port[BUFSIZE];
- #endif
- static inline serial_index_t command_port() { return TERN0(HAS_MULTI_SERIAL, port[index_r]); }
+ struct RingBuffer
+ {
+ /**
+ * Number of commands in the queue
+ */
+ uint8_t length;
+ /**
+ * Ring buffer's read position
+ */
+ uint8_t index_r;
+ /**
+ * Ring buffer's write position
+ */
+ uint8_t index_w;
+ /**
+ * The ring buffer of commands
+ */
+ CommandQueue commands[BUFSIZE];
+
+ inline serial_index_t command_port() { return TERN0(HAS_MULTI_SERIAL, commands[index_r].port); }
+
+ inline void clear() { length = index_r = index_w = 0; }
+
+ void advance_pos(uint8_t & p, int inc) { if (++p >= BUFSIZE) p = 0; length += inc; }
+
+ void commit_command(bool skip_ok
+ #if HAS_MULTI_SERIAL
+ , serial_index_t serial_ind = -1
+ #endif
+ );
+
+ bool enqueue(const char* cmd, bool skip_ok = true
+ #if HAS_MULTI_SERIAL
+ , serial_index_t serial_ind = -1
+ #endif
+ );
+
+ void ok_to_send();
+
+ inline bool full() const { return length >= BUFSIZE; }
- GCodeQueue();
+ inline bool empty() const { return length == 0; }
+ };
+
+ /**
+ * The ring buffer of commands
+ */
+ static RingBuffer ring_buffer;
/**
* Clear the Marlin command queue
*/
- static void clear();
+ static void clear() { ring_buffer.clear(); }
/**
* Next Injected Command (PROGMEM) pointer. (nullptr == empty)
@@ -112,7 +195,7 @@ class GCodeQueue {
/**
* Check whether there are any commands yet to be executed
*/
- static bool has_commands_queued();
+ static bool has_commands_queued() { return ring_buffer.length || injected_commands_P || injected_commands[0]; }
/**
* Get the next command in the queue, optionally log it to SD, then dispatch it
@@ -136,7 +219,7 @@ class GCodeQueue {
* P Planner space remaining
* B Block queue space remaining
*/
- static void ok_to_send();
+ static inline void ok_to_send() { ring_buffer.ok_to_send(); }
/**
* Clear the serial line and request a resend of
@@ -144,9 +227,15 @@ class GCodeQueue {
*/
static void flush_and_request_resend();
+ /**
+ * (Re)Set the current line number for the last received command
+ */
+ static inline void set_current_line_number(long n) { per_serial[ring_buffer.command_port()].last_N = n; }
+
private:
- static uint8_t index_w; // Ring buffer write position
+ /** Get the next command from the buffer (or NULL) */
+ CommandQueue & peek_next_command() { return ring_buffer.commands[ring_buffer.index_r]; }
static void get_serial_commands();
@@ -154,18 +243,6 @@ class GCodeQueue {
static void get_sdcard_commands();
#endif
- static void _commit_command(bool say_ok
- #if HAS_MULTI_SERIAL
- , serial_index_t serial_ind=-1
- #endif
- );
-
- static bool _enqueue(const char* cmd, bool say_ok=false
- #if HAS_MULTI_SERIAL
- , serial_index_t serial_ind=-1
- #endif
- );
-
// Process the next "immediate" command (PROGMEM)
static bool process_injected_command_P();
@@ -180,6 +257,7 @@ class GCodeQueue {
static void gcode_line_error(PGM_P const err, const serial_index_t serial_ind);
+ friend class GcodeSuite;
};
extern GCodeQueue queue;