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;