diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index cd8a055ffb206..bd827f3347c1c 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1173,6 +1173,8 @@
//#define SDCARD_READONLY // Read-only SD card (to save over 2K of flash)
+ //#define GCODE_REPEAT_MARKERS // Enable G-code M41 to set repeat markers and do looping
+
#define SD_PROCEDURE_DEPTH 1 // Increase if you need more nested M32 calls
#define SD_FINISHED_STEPPERRELEASE true // Disable steppers when SD Print is finished
diff --git a/Marlin/src/feature/repeat.cpp b/Marlin/src/feature/repeat.cpp
new file mode 100644
index 0000000000000..b83c66b404b34
--- /dev/null
+++ b/Marlin/src/feature/repeat.cpp
@@ -0,0 +1,68 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+#include "../inc/MarlinConfig.h"
+
+#if ENABLED(GCODE_REPEAT_MARKERS)
+
+#include "repeat.h"
+
+#include "../gcode/gcode.h"
+#include "../sd/cardreader.h"
+
+repeat_marker_t Repeat::marker[MAX_REPEAT_NESTING];
+int8_t Repeat::index;
+
+bool Repeat::early_parse_M41(char * const cmd, const uint32_t &sdpos) {
+ parser.parse(cmd);
+ if (parser.is_command('M', 41)) {
+ if (sdpos) {
+ gcode.process_parsed_command();
+ marker[index].sdpos = sdpos;
+ }
+ return false;
+ }
+ return true;
+}
+
+void Repeat::add_marker(const uint16_t count) {
+ if (index < MAX_REPEAT_NESTING) {
+ marker[index].counter = count ? count : -1;
+ index++;
+ }
+}
+
+void Repeat::loop() {
+ if (!index) // No marker has been set?
+ card.setIndex(0); // Go back to the top
+ else {
+ const uint16_t ind = index - 1; // The relevant marker index
+ if (!marker[ind].counter) // Did the counter run out?
+ --index; // Carry on and loop elsewhere on the next 'M41'
+ else {
+ card.setIndex(marker[ind].sdpos); // Loop back to the marker.
+ if (marker[ind].counter > 0) // Don't decrement a negative (or zero) counter.
+ --marker[ind].counter; // Decrement the counter. If zero this 'M41' will be skipped next time.
+ }
+ }
+}
+
+#endif // GCODE_REPEAT_MARKERS
diff --git a/Marlin/src/feature/repeat.h b/Marlin/src/feature/repeat.h
new file mode 100644
index 0000000000000..412d9310816be
--- /dev/null
+++ b/Marlin/src/feature/repeat.h
@@ -0,0 +1,44 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfigPre.h"
+
+#include
+
+#define MAX_REPEAT_NESTING 10
+
+typedef struct {
+ uint32_t sdpos; // The repeat file position
+ int16_t counter; // The counter for looping
+} repeat_marker_t;
+
+class Repeat {
+public:
+ static repeat_marker_t marker[MAX_REPEAT_NESTING];
+ static int8_t index;
+ static bool early_parse_M41(char * const cmd, const uint32_t &sdpos);
+ static void add_marker(const uint16_t count);
+ static void loop();
+};
+
+extern Repeat repeat;
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 1d6bd94231754..86ee2e3fa9b0d 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -250,7 +250,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
* Will still block Gcodes if M511 is disabled, in which case the printer should be unlocked via LCD Menu
*/
#if ENABLED(PASSWORD_FEATURE)
- if (password.is_locked && !(parser.command_letter == 'M' && parser.codenum == 511)) {
+ if (password.is_locked && !parser.is_command('M', 511)) {
SERIAL_ECHO_MSG(STR_PRINTER_LOCKED);
return;
}
@@ -449,6 +449,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 31: M31(); break; // M31: Report time since the start of SD print or last M109
+ #if ENABLED(GCODE_REPEAT_MARKERS)
+ case 41: M41(); break; // M41: Set / Goto repeat markers
+ #endif
+
#if ENABLED(DIRECT_PIN_CONTROL)
case 42: M42(); break; // M42: Change pin state
#endif
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index 5db8b08e08d45..939bbd0cbb885 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -548,6 +548,8 @@ class GcodeSuite {
#endif
#endif
+ TERN_(GCODE_REPEAT_MARKERS, static void M41());
+
TERN_(DIRECT_PIN_CONTROL, static void M42());
TERN_(PINS_DEBUGGING, static void M43());
diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp
index 529b1dd6d0127..e38481523db21 100644
--- a/Marlin/src/gcode/host/M115.cpp
+++ b/Marlin/src/gcode/host/M115.cpp
@@ -117,6 +117,9 @@ void GcodeSuite::M115() {
// SDCARD (M20, M23, M24, etc.)
cap_line(PSTR("SDCARD"), ENABLED(SDSUPPORT));
+ // REPEAT (M41)
+ cap_line(PSTR("REPEAT"), ENABLED(GCODE_REPEAT_MARKERS));
+
// AUTOREPORT_SD_STATUS (M27 extension)
cap_line(PSTR("AUTOREPORT_SD_STATUS"), ENABLED(AUTO_REPORT_SD_STATUS));
diff --git a/Marlin/src/gcode/parser.cpp b/Marlin/src/gcode/parser.cpp
index bba64dbbc4051..4bff045e304d0 100644
--- a/Marlin/src/gcode/parser.cpp
+++ b/Marlin/src/gcode/parser.cpp
@@ -45,7 +45,7 @@ char *GCodeParser::command_ptr,
*GCodeParser::string_arg,
*GCodeParser::value_ptr;
char GCodeParser::command_letter;
-int GCodeParser::codenum;
+uint16_t GCodeParser::codenum;
#if ENABLED(USE_GCODE_SUBCODES)
uint8_t GCodeParser::subcode;
@@ -270,7 +270,7 @@ void GCodeParser::parse(char *p) {
// Special handling for M32 [P] !/path/to/file.g#
// The path must be the last parameter
- if (param == '!' && letter == 'M' && codenum == 32) {
+ if (param == '!' && is_command('M', 32)) {
string_arg = p; // Name starts after '!'
char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering)
if (lb) *lb = '\0'; // Safe to mark the end of the filename
diff --git a/Marlin/src/gcode/parser.h b/Marlin/src/gcode/parser.h
index 17fb084388902..69bbdaf02d398 100644
--- a/Marlin/src/gcode/parser.h
+++ b/Marlin/src/gcode/parser.h
@@ -84,7 +84,7 @@ class GCodeParser {
static char *command_ptr, // The command, so it can be echoed
*string_arg, // string of command line
command_letter; // G, M, or T
- static int codenum; // 123
+ static uint16_t codenum; // 123
#if ENABLED(USE_GCODE_SUBCODES)
static uint8_t subcode; // .1
#endif
@@ -244,6 +244,9 @@ class GCodeParser {
static bool chain();
#endif
+ // Test whether the parsed command matches the input
+ static inline bool is_command(const char ltr, const uint16_t num) { return command_letter == ltr && codenum == num; }
+
// The code value pointer was set
FORCE_INLINE static bool has_value() { return !!value_ptr; }
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index f481052cbf4f4..3d9b552a6c587 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -51,6 +51,10 @@ GCodeQueue queue;
#include "../feature/powerloss.h"
#endif
+#if ENABLED(GCODE_REPEAT_MARKERS)
+ #include "../feature/repeat.h"
+#endif
+
/**
* GCode line number handling. Hosts may opt to include line numbers when
* sending commands to Marlin, and lines will be checked for sequentiality.
@@ -416,11 +420,14 @@ inline void process_stream_char(const char c, uint8_t &sis, char (&buff)[MAX_CMD
* keep sensor readings going and watchdog alive.
*/
inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
- sis = PS_NORMAL;
- buff[ind] = 0;
- if (ind) { ind = 0; return false; }
- thermalManager.manage_heater();
- return true;
+ sis = PS_NORMAL; // "Normal" Serial Input State
+ buff[ind] = '\0'; // Of course, I'm a Terminator.
+ const bool is_empty = (ind == 0); // An empty line?
+ if (is_empty)
+ thermalManager.manage_heater(); // Keep sensors satisfied
+ else
+ ind = 0; // Start a new line
+ return is_empty; // Inform the caller
}
/**
@@ -547,12 +554,14 @@ void GCodeQueue::get_serial_commands() {
last_command_time = ms;
#endif
- // Add the command to the queue
- _enqueue(serial_line_buffer[i], true
- #if HAS_MULTI_SERIAL
- , i
- #endif
- );
+ if (TERN1(GCODE_REPEAT_MARKERS, repeat.early_parse_M41(serial_line_buffer[i], 0))) {
+ // Add the command to the queue
+ _enqueue(serial_line_buffer[i], true
+ #if HAS_MULTI_SERIAL
+ , i
+ #endif
+ );
+ }
}
else
process_stream_char(serial_char, serial_input_state[i], serial_line_buffer[i], serial_count[i]);
@@ -588,10 +597,9 @@ void GCodeQueue::get_serial_commands() {
// 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)) {
- _commit_command(false);
- #if ENABLED(POWER_LOSS_RECOVERY)
- recovery.cmd_sdpos = card.getIndex(); // Prime for the NEXT _commit_command
- #endif
+ if (TERN1(GCODE_REPEAT_MARKERS, repeat.early_parse_M41(command_buffer[index_w], card.getIndex())))
+ _commit_command(false);
+ TERN_(POWER_LOSS_RECOVERY, recovery.cmd_sdpos = card.getIndex()); // Prime for the NEXT _commit_command
}
if (card_eof) card.fileHasFinished(); // Handle end of file reached
diff --git a/Marlin/src/gcode/sd/M41.cpp b/Marlin/src/gcode/sd/M41.cpp
new file mode 100644
index 0000000000000..baaef70f08059
--- /dev/null
+++ b/Marlin/src/gcode/sd/M41.cpp
@@ -0,0 +1,53 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(GCODE_REPEAT_MARKERS)
+
+#include "../gcode.h"
+#include "../../feature/repeat.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M41: Set / Goto a repeat marker
+ *
+ * S - Set a repeat marker with 'count' repetitions. If omitted, infinity.
+ *
+ * Examples:
+ *
+ * M41 S ; Set a loop marker with a count of infinity
+ * M41 S2 ; Set a loop marker with a count of 2
+ * M41 ; Decrement and loop if not zero.
+ */
+void GcodeSuite::M41() {
+
+ if (!IS_SD_PRINTING()) return;
+
+ if (parser.seen('S')) // If 'S' was seen...
+ repeat.add_marker(parser.value_ushort()); // Add a marker with a count. 'S' or 'S0' = forever.
+ else
+ repeat.loop();
+
+}
+
+#endif // GCODE_REPEAT_MARKERS
diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h
index d0b66aee457cc..ad32fd1feaa71 100644
--- a/Marlin/src/lcd/marlinui.h
+++ b/Marlin/src/lcd/marlinui.h
@@ -84,10 +84,6 @@
inline void wrap_string_P(uint8_t &col, uint8_t &row, PGM_P const pstr, const bool wordwrap=false) { _wrap_string(col, row, pstr, read_byte_rom, wordwrap); }
inline void wrap_string(uint8_t &col, uint8_t &row, const char * const string, const bool wordwrap=false) { _wrap_string(col, row, string, read_byte_ram, wordwrap); }
- #if ENABLED(SDSUPPORT)
- #include "../sd/cardreader.h"
- #endif
-
typedef void (*screenFunc_t)();
typedef void (*menuAction_t)();
diff --git a/buildroot/tests/mega2560-tests b/buildroot/tests/mega2560-tests
index a6902b9d144b5..f8f7acae72b69 100755
--- a/buildroot/tests/mega2560-tests
+++ b/buildroot/tests/mega2560-tests
@@ -82,7 +82,7 @@ restore_configs
opt_set MOTHERBOARD BOARD_MEGACONTROLLER
opt_set LCD_LANGUAGE de
opt_enable EEPROM_SETTINGS EEPROM_CHITCHAT \
- MINIPANEL SDSUPPORT PCA9632 LCD_INFO_MENU SOUND_MENU_ITEM \
+ MINIPANEL SDSUPPORT PCA9632 LCD_INFO_MENU SOUND_MENU_ITEM GCODE_REPEAT_MARKERS \
AUTO_BED_LEVELING_BILINEAR PROBE_MANUALLY LCD_BED_LEVELING G26_MESH_VALIDATION MESH_EDIT_MENU \
LIN_ADVANCE EXTRA_LIN_ADVANCE_K \
INCH_MODE_SUPPORT TEMPERATURE_UNITS_SUPPORT EXPERIMENTAL_I2CBUS M100_FREE_MEMORY_WATCHER \
diff --git a/platformio.ini b/platformio.ini
index 15b8d4c23a644..4e4c278574b99 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -179,8 +179,7 @@ default_src_filter = + - - +
-
-
-
- -
- -
+ - - -
-
-
-
@@ -372,6 +371,7 @@ G38_PROBE_TARGET = src_filter=+
MAGNETIC_PARKING_EXTRUDER = src_filter=+
SDSUPPORT = src_filter=+
HAS_MEDIA_SUBCALLS = src_filter=+
+GCODE_REPEAT_MARKERS = src_filter=+
HAS_EXTRUDERS = src_filter=+ +
AUTO_REPORT_TEMPERATURES = src_filter=+
INCH_MODE_SUPPORT = src_filter=+